diff --git a/component/fontconfig/buildout.cfg b/component/fontconfig/buildout.cfg index 06ba0b8d62bd5dee820cbff7935b3c35f1ccceb2..a621f3a21a3c40e4433807fd9a973a969cae3981 100644 --- a/component/fontconfig/buildout.cfg +++ b/component/fontconfig/buildout.cfg @@ -18,12 +18,12 @@ shared = true url = http://fontconfig.org/release/fontconfig-2.12.6.tar.bz2 md5sum = 733f5e2371ca77b69707bd7b30cc2163 pkg_config_depends = ${freetype:pkg_config_depends}:${freetype:location}/lib/pkgconfig:${libxml2:location}/lib/pkgconfig -# XXX-Cedric : should we use --with-add-fonts={somefont:location}/share,{someotherfont:location}/share? configure-options = --disable-static --disable-docs --enable-libxml2 --with-default-fonts=${fonts:location} + --with-add-fonts=no environment = PATH=${pkgconfig:location}/bin:${gperf:location}/bin:%(PATH)s PKG_CONFIG_PATH=${:pkg_config_depends} diff --git a/component/fonts/buildout.cfg b/component/fonts/buildout.cfg index a6ed31152817e722b8bdefd194eb3b6365816196..0475f88281ea13303c646943b988c388bf6f23aa 100644 --- a/component/fonts/buildout.cfg +++ b/component/fonts/buildout.cfg @@ -9,6 +9,7 @@ parts = ipa-fonts ocrb-fonts android-fonts + dejavu-fonts [fonts] location = ${buildout:parts-directory}/${:_buildout_section_name_} @@ -50,6 +51,13 @@ md5sum = 2d41d5342eb5f61591ddeec5b80da74d environment = PATH=${xz-utils:location}/bin:%(PATH)s +# The DejaVu fonts are a font family based upon Bitstream Vera v1.10. Its purpose is to +# provide a wider range of characters while maintaining the original look-and-feel +[dejavu-fonts] +<= fonts-base +url = https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_2_37/dejavu-fonts-ttf-2.37.tar.bz2 +md5sum = d0efec10b9f110a32e9b8f796e21782c + # Microsoft's TrueType core fonts # non-free so not enabled by default [msttcore-fonts] diff --git a/component/graphviz/buildout.cfg b/component/graphviz/buildout.cfg index 83d597df4cc281041de7943d0a44f2f92b000e1a..bcfa2d026dfe98d09e32b7d03536019a228a4cfb 100644 --- a/component/graphviz/buildout.cfg +++ b/component/graphviz/buildout.cfg @@ -9,12 +9,13 @@ extends = ../gtk-2/buildout.cfg ../pkgconfig/buildout.cfg ../zlib/buildout.cfg + ../libexpat/buildout.cfg [graphviz] recipe = slapos.recipe.cmmi shared = true -url = http://www.graphviz.org/pub/graphviz/stable/SOURCES/graphviz-2.38.0.tar.gz -md5sum = 5b6a829b2ac94efcd5fa3c223ed6d3ae +url = https://ftp.osuosl.org/pub/blfs/conglomeration/graphviz/graphviz-2.40.1.tar.gz +md5sum = 4ea6fd64603536406166600bcc296fc8 pkg_config_depends = ${pango:location}/lib/pkgconfig:${pango:pkg_config_depends} configure-options = --with-included-ltdl @@ -22,6 +23,7 @@ configure-options = --with-zlibdir=${zlib:location}/lib --with-freetype2 --with-fontconfig + --with-expat --disable-swig --disable-sharp --disable-go @@ -37,7 +39,6 @@ configure-options = --disable-ruby --disable-tcl --without-x - --without-expat --without-devil --without-webp --without-poppler @@ -59,5 +60,5 @@ configure-options = environment = PATH=${pkgconfig:location}/bin:%(PATH)s PKG_CONFIG_PATH=${:pkg_config_depends} - CPPFLAGS=-I${zlib:location}/include - LDFLAGS=-L${bzip2:location}/lib -Wl,-rpath=${bzip2:location}/lib -L${zlib:location}/lib -Wl,-rpath=${zlib:location}/lib + CPPFLAGS=-I${zlib:location}/include -I${libexpat:location}/include + LDFLAGS=-L${bzip2:location}/lib -Wl,-rpath=${bzip2:location}/lib -L${zlib:location}/lib -Wl,-rpath=${zlib:location}/lib -L${libexpat:location}/lib -Wl,-rpath=${libexpat:location}/lib diff --git a/component/tomcat/buildout.cfg b/component/tomcat/buildout.cfg index 0a0d98b500d8b908f086fe2290993027888df606..99da212c2969df600137d59a521d06ab9c287a8d 100644 --- a/component/tomcat/buildout.cfg +++ b/component/tomcat/buildout.cfg @@ -23,6 +23,13 @@ strip-top-level-dir = true url = http://www-us.apache.org/dist/tomcat/tomcat-7/v7.0.91/bin/apache-tomcat-7.0.91.tar.gz md5sum = 8bfbb358b51f90374067879f8db1e91c +[tomcat9] +recipe = hexagonit.recipe.download +ignore-existing = true +strip-top-level-dir = true +url = https://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.12/bin/apache-tomcat-9.0.12.tar.gz +md5sum = 7283da4a3a6e939adcd8f919be4ba41a + [tomcat7-output] # Shared binary location to ease migration recipe = plone.recipe.command diff --git a/software/erp5testnode/testsuite/plantuml/README.md b/software/erp5testnode/testsuite/plantuml/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7228b2f6651aca868e580dcde53c0920805c0127 --- /dev/null +++ b/software/erp5testnode/testsuite/plantuml/README.md @@ -0,0 +1,7 @@ +# PlantUML test + +This software release is simply to run the test suite from `../../plantuml/test/setup.py` + +Nexedi staff can see the results of this test from the test suite +`SLAPOS-PLANTUML-TEST` in test result module. + diff --git a/software/erp5testnode/testsuite/plantuml/buildout.hash.cfg b/software/erp5testnode/testsuite/plantuml/buildout.hash.cfg new file mode 100644 index 0000000000000000000000000000000000000000..50bcdcc84d552919d3ecc9b3da829b6949bfd8c6 --- /dev/null +++ b/software/erp5testnode/testsuite/plantuml/buildout.hash.cfg @@ -0,0 +1,19 @@ +# THIS IS NOT A BUILDOUT FILE, despite purposedly using a compatible syntax. +# The only allowed lines here are (regexes): +# - "^#" comments, copied verbatim +# - "^[" section beginings, copied verbatim +# - lines containing an "=" sign which must fit in the following categorie. +# - "^\s*filename\s*=\s*path\s*$" where "path" is relative to this file +# But avoid directories, they are not portable. +# Copied verbatim. +# - "^\s*hashtype\s*=.*" where "hashtype" is one of the values supported +# by the re-generation script. +# Re-generated. +# - other lines are copied verbatim +# Substitution (${...:...}), extension ([buildout] extends = ...) and +# section inheritance (< = ...) are NOT supported (but you should really +# not need these here). + +[template] +filename = instance.cfg.in +md5sum = 4ab7207a0440a904b4374add9b744312 diff --git a/software/erp5testnode/testsuite/plantuml/instance.cfg.in b/software/erp5testnode/testsuite/plantuml/instance.cfg.in new file mode 100644 index 0000000000000000000000000000000000000000..fc0a8c12f1ed8299f165a9552c45a61cda8b8c0a --- /dev/null +++ b/software/erp5testnode/testsuite/plantuml/instance.cfg.in @@ -0,0 +1,43 @@ +[buildout] +parts = + slapos-test-runner + +eggs-directory = ${buildout:eggs-directory} +develop-eggs-directory = ${buildout:develop-eggs-directory} +offline = true + +[slap-configuration] +recipe = slapos.cookbook:slapconfiguration +computer = $${slap-connection:computer-id} +partition = $${slap-connection:partition-id} +url = $${slap-connection:server-url} +key = $${slap-connection:key-file} +cert = $${slap-connection:cert-file} + +[download-source] +recipe = slapos.recipe.build:gitclone +git-executable = ${git:location}/bin/git + +[slapos] +<= download-source +repository = ${slapos-repository:location} + +[create-directory] +recipe = slapos.cookbook:mkdirectory +bin = $${buildout:directory}/bin +working-dir = $${buildout:directory}/tmp/ + +[slapos-test-runner] +recipe = slapos.cookbook:wrapper +wrapper-path = $${create-directory:bin}/runTestSuite +command-line = + ${buildout:bin-directory}/runTestSuite + --python_interpreter=${buildout:bin-directory}/${eggs:interpreter} + --source_code_path_list=$${slapos:location}/software/plantuml/test + +# XXX slapos.cookbook:wrapper does not allow extending env, so we add some default $PATH entries +environment = + PATH=${buildout:bin-directory}:/usr/bin/:/bin/ + LOCAL_IPV4=$${slap-configuration:ipv4-random} + GLOBAL_IPV6=$${slap-configuration:ipv6-random} + SLAPOS_TEST_WORKING_DIR=$${create-directory:working-dir} diff --git a/software/erp5testnode/testsuite/plantuml/software.cfg b/software/erp5testnode/testsuite/plantuml/software.cfg new file mode 100644 index 0000000000000000000000000000000000000000..90f759412be5a44b97ead0db8997030305541273 --- /dev/null +++ b/software/erp5testnode/testsuite/plantuml/software.cfg @@ -0,0 +1,58 @@ +[buildout] + +extends = + ../../../../component/git/buildout.cfg + ../../../../component/pillow/buildout.cfg + ../../../../stack/slapos.cfg + ./buildout.hash.cfg + +parts = + slapos-cookbook + eggs + template + +[setup-develop-egg] +recipe = zc.recipe.egg:develop + +[slapos.test.plantuml-setup] +<= setup-develop-egg +egg = slapos.test.plantuml +setup = ${slapos-repository:location}/software/plantuml/test/ + +[eggs] +recipe = zc.recipe.egg +eggs = + ${pillow-python:egg} + ${slapos.test.plantuml-setup:egg} + slapos.core +entry-points = + runTestSuite=erp5.util.testsuite:runTestSuite +scripts = + runTestSuite + slapos +interpreter= + python_for_test + +[git-clone-repository] +recipe = slapos.recipe.build:gitclone +git-executable = ${git:location}/bin/git +forbid-download-cache = true +branch = master + +[slapos-repository] +<= git-clone-repository +repository = https://lab.nexedi.com/nexedi/slapos.git + +[template] +recipe = slapos.recipe.template +url = ${:_profile_base_location_}/${:filename} +output = ${buildout:directory}/template.cfg +mode = 640 + +[versions] +erp5.util = 0.4.56 +slapos.recipe.template = 4.3 +plantuml = 0.1.1 +httplib2 = 0.11.3 +image = 1.5.25 +Pillow = 5.3.0 \ No newline at end of file diff --git a/software/plantuml/README.md b/software/plantuml/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a86e3eb1527449e02632325b4cae2200099b772f --- /dev/null +++ b/software/plantuml/README.md @@ -0,0 +1,14 @@ +# PlantUML + +http://plantuml.com/ + +PlantUML is a service rendering UML diagrams defined in a simple and intuitive +language. + +Each diagram has a unique URL which is made of an encoded version of the +diagram code. + +Diagrams can be rendered as png, svg or ascii art text. + +See http://plantuml.com/PlantUML_Language_Reference_Guide.pdf for a full +reference on the diagram language. diff --git a/software/plantuml/buildout.hash.cfg b/software/plantuml/buildout.hash.cfg new file mode 100644 index 0000000000000000000000000000000000000000..3c9bd1c32243ff58d61d088229283f40ddccda17 --- /dev/null +++ b/software/plantuml/buildout.hash.cfg @@ -0,0 +1,26 @@ +# THIS IS NOT A BUILDOUT FILE, despite purposedly using a compatible syntax. +# The only allowed lines here are (regexes): +# - "^#" comments, copied verbatim +# - "^[" section beginings, copied verbatim +# - lines containing an "=" sign which must fit in the following categorie. +# - "^\s*filename\s*=\s*path\s*$" where "path" is relative to this file +# Copied verbatim. +# - "^\s*hashtype\s*=.*" where "hashtype" is one of the values supported +# by the re-generation script. +# Re-generated. +# - other lines are copied verbatim +# Substitution (${...:...}), extension ([buildout] extends = ...) and +# section inheritance (< = ...) are NOT supported (but you should really +# not need these here). + +[instance] +filename = instance.cfg.in +md5sum = 8915151103355dd59da31979a14e59fd + +[tomcat-server-xml] +filename = server.xml.in +md5sum = fdfa7eb249082855039ca98f310324e9 + +[font.conf] +filename = font.conf.in +md5sum = caa3463c9c3766ac5f2396a517d6f926 diff --git a/software/plantuml/font.conf.in b/software/plantuml/font.conf.in new file mode 100644 index 0000000000000000000000000000000000000000..5c0ad3759e1d77e646f4d26a0c7d2e679c11039c --- /dev/null +++ b/software/plantuml/font.conf.in @@ -0,0 +1,9 @@ +<?xml version="1.0"?> +<!DOCTYPE fontconfig SYSTEM "fonts.dtd"> +<fontconfig> + <cachedir>$${:fontcache}</cachedir> + <!-- installed fonts: $${:installed-fonts} --> + <dir>${fonts:location}</dir> + <dir>$${:fonts}</dir> + <include>${fontconfig:location}/etc/fonts/conf.d</include> +</fontconfig> \ No newline at end of file diff --git a/software/plantuml/instance.cfg.in b/software/plantuml/instance.cfg.in new file mode 100644 index 0000000000000000000000000000000000000000..a61c467517f55159a2b5572277f1a230ee10e171 --- /dev/null +++ b/software/plantuml/instance.cfg.in @@ -0,0 +1,123 @@ +[buildout] +parts = + promises + publish-connection-parameter + +eggs-directory = ${buildout:eggs-directory} +develop-eggs-directory = ${buildout:develop-eggs-directory} +offline = true + +[fontconfig-conf] +recipe = slapos.recipe.template +url = ${font.conf:output} +output = $${directory:etc}/font.conf + +fonts = $${directory:fonts} +fontcache = $${directory:fontcache} +installed-fonts = + ${liberation-fonts:location} + ${ipaex-fonts:location} + ${ipa-fonts:location} + ${ocrb-fonts:location} + ${android-fonts:location} + ${dejavu-fonts:location} + +[tomcat-server-xml] +recipe = slapos.recipe.template +url = ${tomcat-server-xml:output} +output = $${directory:catalina_conf}/server.xml +ip = $${instance-parameter:ipv6-random} +port = 8899 +scheme = https + +[tomcat-web-xml] +recipe = plone.recipe.command +command = [ -f $${:location} ] || cp ${tomcat9:location}/conf/web.xml $${:location} +location = $${directory:catalina_conf}/web.xml + +[tomcat-keystore] +recipe = plone.recipe.command +command = + ${java-re-8-output:keytool} \ + -genkeypair \ + -alias "tomcat" \ + -keyalg RSA \ + -keypass "$${:pass}" \ + -dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=Country" \ + -keystore "$${:file}" \ + -storepass "$${:pass}" +file = $${directory:catalina_base}/.keystore +pass = insecure + +[tomcat-instance] +recipe = slapos.cookbook:wrapper +wrapper-path = $${directory:services}/$${:_buildout_section_name_} +command-line = ${tomcat9:location}/bin/catalina.sh run +environment = + JRE_HOME=${java-re-8:location} + CATALINA_BASE=$${directory:catalina_base} + GRAPHVIZ_DOT=${graphviz:location}/bin/dot + FONTCONFIG_FILE=$${fontconfig-conf:output} + LD_LIBRARY_PATH=${fontconfig:location}/lib:${freetype:location}/lib +# XXX java is still loading system fonts ... ( even with $JAVA_FONTS or -Djava.awt.fonts ) +# related links: +# https://docs.oracle.com/javase/8/docs/technotes/guides/intl/fontconfig.html +# https://bugs.openjdk.java.net/browse/JDK-7175487 +hash-files = + $${buildout:directory}/software_release/buildout.cfg + $${tomcat-server-xml:output} + +ip = $${tomcat-server-xml:ip} +port = $${tomcat-server-xml:port} +scheme = $${tomcat-server-xml:scheme} +hostname = [$${:ip}] +url = $${:scheme}://$${:hostname}:$${:port} +needs = $${tomcat-web-xml:location} + +[promises] +recipe = +instance-promises = + $${tomcat-listen-promise:path} + +[check-port-listening-promise] +recipe = slapos.cookbook:check_port_listening +path = $${directory:promises}/$${:_buildout_section_name_} + +[tomcat-listen-promise] +<= check-port-listening-promise +hostname= $${tomcat-instance:ip} +port = $${tomcat-instance:port} + +[publish-connection-parameter] +recipe = slapos.cookbook:publish +url = $${tomcat-instance:url} + + +[instance-parameter] +recipe = slapos.cookbook:slapconfiguration +computer = $${slap-connection:computer-id} +partition = $${slap-connection:partition-id} +url = $${slap-connection:server-url} +key = $${slap-connection:key-file} +cert = $${slap-connection:cert-file} + +[directory] +recipe = slapos.cookbook:mkdirectory +etc = $${buildout:directory}/etc +var = $${buildout:directory}/var +srv = $${buildout:directory}/srv +bin = $${buildout:directory}/bin +tmp = $${buildout:directory}/tmp +services = $${:etc}/service +promises = $${:etc}/promise + +fonts = $${:srv}/fonts/ +fontcache = $${buildout:directory}/.fontcache/ + +# tomcat directories +catalina_base = $${:var}/tomcat +catalina_logs = $${:catalina_base}/logs +catalina_temp = $${:catalina_base}/temp +catalina_webapps = $${:catalina_base}/webapps +catalina_work = $${:catalina_base}/work +catalina_conf = $${:catalina_base}/conf \ No newline at end of file diff --git a/software/plantuml/server.xml.in b/software/plantuml/server.xml.in new file mode 100644 index 0000000000000000000000000000000000000000..a22254729b8be55d19bf8905e83297d0987d1309 --- /dev/null +++ b/software/plantuml/server.xml.in @@ -0,0 +1,30 @@ +<?xml version='1.0' encoding='utf-8'?> +<Server port="-1" shutdown="SHUTDOWN"> + <Service name="Catalina"> + <Connector + protocol="org.apache.coyote.http11.Http11Nio2Protocol" + sslImplementationName="org.apache.tomcat.util.net.jsse.JSSEImplementation" + address="$${tomcat-server-xml:ip}" + port="$${tomcat-server-xml:port}" + maxThreads="10" + scheme="$${tomcat-server-xml:scheme}" + secure="true" + clientAuth="false" + SSLEnabled="true" + keystorePass="$${tomcat-keystore:pass}" + keystoreFile="$${tomcat-keystore:file}" + /> + + <Engine name="Catalina" defaultHost="localhost"> + <Valve className="org.apache.catalina.valves.AccessLogValve" + directory="logs" prefix="localhost_access_log." suffix=".log" + pattern="common"/> + <Host name="localhost" appBase="webapps" + unpackWARs="true" autoDeploy="true"> + <Context path="" docBase="${plantuml.war:location}/plantuml.war" + privileged="true"> + </Context> + </Host> + </Engine> + </Service> +</Server> \ No newline at end of file diff --git a/software/plantuml/software.cfg b/software/plantuml/software.cfg new file mode 100644 index 0000000000000000000000000000000000000000..46fe92a823b3521f02242ad16711015b2bd15635 --- /dev/null +++ b/software/plantuml/software.cfg @@ -0,0 +1,39 @@ +[buildout] +extends = + ../../stack/slapos.cfg + ../../stack/nodejs.cfg + ../../component/fontconfig/buildout.cfg + ../../component/freetype/buildout.cfg + ../../component/graphviz/buildout.cfg + ../../component/java/buildout.cfg + ../../component/tomcat/buildout.cfg + ../../component/fonts/buildout.cfg + buildout.hash.cfg + +parts = + slapos-cookbook + instance + +[instance] +recipe = slapos.recipe.template +url = ${:_profile_base_location_}/${:filename} +output = ${buildout:directory}/instance.cfg + +[tomcat-server-xml] +recipe = slapos.recipe.template +url = ${:_profile_base_location_}/${:filename} +output = ${buildout:directory}/${:_buildout_section_name_} + +[font.conf] +recipe = slapos.recipe.template +url = ${:_profile_base_location_}/${:filename} +output = ${buildout:directory}/${:_buildout_section_name_} + +[plantuml.war] +recipe = slapos.recipe.build:download +# XXX the war from sourceforge has no version in URL +url = https://netcologne.dl.sourceforge.net/project/plantuml/plantuml.war#2018-10-21 +md5sum = f956cd28b18ec34740bb1757276f9641 + +[versions] +slapos.recipe.template = 4.3 diff --git a/software/plantuml/test/README.md b/software/plantuml/test/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a8045b99fb8d2e25a970af4028049d69687b1452 --- /dev/null +++ b/software/plantuml/test/README.md @@ -0,0 +1 @@ +Tests for PlantUML software release diff --git a/software/plantuml/test/data/test_ascii_art.txt b/software/plantuml/test/data/test_ascii_art.txt new file mode 100644 index 0000000000000000000000000000000000000000..f6d896b5c4ea419cb09c510729055058419387a7 --- /dev/null +++ b/software/plantuml/test/data/test_ascii_art.txt @@ -0,0 +1,11 @@ + ┌───┠┌─────┠+ │Bob│ │Alice│ + └─┬─┘ └──┬──┘ + │ hello │ + │──────────────>│ + │ │ + │ Go Away │ + │<──────────────│ + ┌─┴─┠┌──┴──┠+ │Bob│ │Alice│ + └───┘ └─────┘ diff --git a/software/plantuml/test/data/test_class_diagram.png b/software/plantuml/test/data/test_class_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..2a3c6d3d17a89914cd44fce81d7b6532e40e3591 Binary files /dev/null and b/software/plantuml/test/data/test_class_diagram.png differ diff --git a/software/plantuml/test/data/test_fonts.png b/software/plantuml/test/data/test_fonts.png new file mode 100644 index 0000000000000000000000000000000000000000..16ac44163916f06235ca7e4b28c4320ce3f40374 Binary files /dev/null and b/software/plantuml/test/data/test_fonts.png differ diff --git a/software/plantuml/test/data/test_sequence_diagram.png b/software/plantuml/test/data/test_sequence_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..f6c7eea1455e33dce075dc2eaa281a1f971f813e Binary files /dev/null and b/software/plantuml/test/data/test_sequence_diagram.png differ diff --git a/software/plantuml/test/setup.py b/software/plantuml/test/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..25925a9a695a83f52296fe15985a5b854749edca --- /dev/null +++ b/software/plantuml/test/setup.py @@ -0,0 +1,55 @@ +############################################################################## +# +# Copyright (c) 2018 Nexedi SA and Contributors. All Rights Reserved. +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsibility of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# guarantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## +from setuptools import setup, find_packages +import glob +import os + +version = '0.0.1.dev0' +name = 'slapos.test.plantuml' +long_description = open("README.md").read() + +setup(name=name, + version=version, + description="Test for SlapOS' PlantUML", + long_description=long_description, + long_description_content_type='text/markdown', + maintainer="Nexedi", + maintainer_email="info@nexedi.com", + url="https://lab.nexedi.com/nexedi/slapos", + packages=find_packages(), + install_requires=[ + 'slapos.core', + 'slapos.libnetworkcache', + 'erp5.util', + 'supervisor', + 'psutil', + 'plantuml', + 'requests' + ], + zip_safe=True, + test_suite='test', + ) diff --git a/software/plantuml/test/test.py b/software/plantuml/test/test.py new file mode 100644 index 0000000000000000000000000000000000000000..f003abeb9c1072249f7ede9189caef431a1c05f6 --- /dev/null +++ b/software/plantuml/test/test.py @@ -0,0 +1,188 @@ +############################################################################## +# coding: utf-8 +# +# Copyright (c) 2018 Nexedi SA and Contributors. All Rights Reserved. +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsibility of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# guarantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import os +import textwrap +import hashlib +from io import BytesIO + +from PIL import Image +import requests +import plantuml + +import utils + +# for development: debugging logs and install Ctrl+C handler +if os.environ.get('DEBUG'): + import logging + logging.basicConfig(level=logging.DEBUG) + import unittest + unittest.installHandler() + + +class PlantUMLTestCase(utils.SlapOSInstanceTestCase): + @classmethod + def getSoftwareURLList(cls): + return (os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'software.cfg')), ) + + +class TestSimpleDiagram(PlantUMLTestCase): + def setUp(self): + self.url = self.computer_partition.getConnectionParameterDict()["url"] + self.plantuml = plantuml.PlantUML( + url='{}/png/'.format(self.url), + http_opts={"disable_ssl_certificate_validation": True} + ) + + def assertImagesSimilar(self, i1, i2, tolerance=5): + """Assert images difference between images is less than `tolerance` %. + taken from https://rosettacode.org/wiki/Percentage_difference_between_images + """ + pairs = zip(i1.getdata(), i2.getdata()) + if len(i1.getbands()) == 1: + # for gray-scale jpegs + dif = sum(abs(p1-p2) for p1,p2 in pairs) + else: + dif = sum(abs(c1-c2) for p1,p2 in pairs for c1,c2 in zip(p1,p2)) + + ncomponents = i1.size[0] * i1.size[1] * 3 + self.assertLessEqual((dif / 255.0 * 100) / ncomponents, tolerance) + + def assertImagesSame(self, i1, i2): + """Assert images are exactly same.""" + self.assertImagesSimilar(i1, i2, 0) + + def test_sequence_diagram(self): + png = self.plantuml.processes(textwrap.dedent("""\ + @startuml + Bob -> Alice : hello + Alice -> Bob : Go Away + @enduml + """)) + # we cannot just compare the hash of the image against a reference that can be found with + # http://www.plantuml.com/plantuml/png/SoWkIImgAStDuNBAJrBGjLDmpCbCJbMmKiX8pSd9vuBmWC8WMIi5ztm5n_B4IYw7rBmKe1u0 + # because plantuml include information about the server in the output image metadata ( you can + # use http://exif.regex.info/exif.cgi to see metadata ) + # So we process the image to remove metadata. + reference = Image.open(os.path.join(os.path.dirname(__file__), "data", "test_sequence_diagram.png")) + self.assertImagesSame(Image.open(BytesIO(png)), reference) + + def test_class_diagram(self): + """Class diagram require a working graphviz installation""" + png = self.plantuml.processes(textwrap.dedent("""\ + @startuml + class Car + + Driver - Car : drives > + Car *- Wheel : have 4 > + Car -- Person : < owns + + @enduml + """)) + # rendering is not exactly same on class diagrams, because of fonts and maybe also something in graphviz. + # We just compare that image are similar. + + # http://www.plantuml.com/plantuml/png/SoWkIImgAStDuKhEIImkLd1EBEBYSYdAB4ijKj05yHIi5590t685EouGLqjN8JmZDJK7A9wHM9QgO08LrzLL24WjAixF0qhOAEINvnLpSJcavgK0ZGO0 + reference = Image.open(os.path.join(os.path.dirname(__file__), "data", "test_class_diagram.png")) + self.assertImagesSimilar(Image.open(BytesIO(png)), reference) + + def test_fonts(self): + """Test slapos provided fonts are used""" + png = self.plantuml.processes(textwrap.dedent("""\ + @startuml + listfonts ç§ã¯ç”³ã—訳ã‚ã‚Šã¾ã›ã‚“:ç§ã¯æ—¥æœ¬èªžã‚’話ã•ãªã„。Je ne parle pas japonais. + @enduml + """)) + # URL on the reference implementation would be + # http://www.plantuml.com/plantuml/png/SoWkIImgAStDuSh9B2v9oyyhALPulhpnSUFwvrCsFswS_c85a6nwtDJrk77VuyRPZviclzyp2wBWsVIbp-QiUR5gtkEcIIzMRdpSEFLnuwh7ZIsF6vgyKXNoKXKA4ejoG6InGbPYGNvUOcQn7fT3QbuAq3O0 + # but we don't have same fonts, so we compare against the fonts of a slapos instance. + reference = Image.open(os.path.join(os.path.dirname(__file__), "data", "test_fonts.png")) + self.assertImagesSimilar(Image.open(BytesIO(png)), reference) + + def test_editor(self): + """Test the embedded editor""" + r = requests.get('{}/uml/'.format(self.url), verify=False) + self.assertEqual(r.status_code, requests.codes.ok) + + def test_svg(self): + """Test svg rendering""" + image_key = plantuml.deflate_and_encode(textwrap.dedent("""\ + @startuml + Bob -> Alice : hello + Alice -> Bob : Go Away + @enduml + """)) + svg = requests.get('{}/svg/{}'.format(self.url, image_key), verify=False).text + self.assertIn('<?xml version="1.0" encoding="UTF-8"', svg) + + def test_ascii_art(self): + """Test ascii art rendering""" + image_key = plantuml.deflate_and_encode(textwrap.dedent("""\ + @startuml + Bob -> Alice : hello + Alice -> Bob : Go Away + @enduml + """)) + aa = requests.get('{}/txt/{}'.format(self.url, image_key), verify=False).content + with open(os.path.join(os.path.dirname(__file__), "data", "test_ascii_art.txt"), 'rb') as reference: + self.assertEqual(aa, reference.read()) + + +class ServicesTestCase(PlantUMLTestCase): + @staticmethod + def generateHashFromFiles(file_list): + hasher = hashlib.md5() + for path in file_list: + with open(path, 'r') as afile: + buf = afile.read() + hasher.update("%s\n" % len(buf)) + hasher.update(buf) + hash = hasher.hexdigest() + return hash + + def test_hashes(self): + hash_files = [ + 'software_release/buildout.cfg', + 'var/tomcat/conf/server.xml' + ] + expected_process_names = [ + 'tomcat-instance-{hash}-on-watch', + ] + + supervisor = self.getSupervisorRPCServer().supervisor + process_names = [process['name'] + for process in supervisor.getAllProcessInfo()] + + hash_files = [os.path.join(self.computer_partition_root_path, path) + for path in hash_files] + + for name in expected_process_names: + h = ServicesTestCase.generateHashFromFiles(hash_files) + expected_process_name = name.format(hash=h) + + self.assertIn(expected_process_name, process_names) diff --git a/software/plantuml/test/utils.py b/software/plantuml/test/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..9590d72bf8ef36755e0096e482c9de421d01d6b4 --- /dev/null +++ b/software/plantuml/test/utils.py @@ -0,0 +1,321 @@ +############################################################################## +# +# Copyright (c) 2018 Nexedi SA and Contributors. All Rights Reserved. +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsibility of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# guarantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import unittest +import os +import socket +from contextlib import closing +import logging +import StringIO +import xmlrpclib + +import supervisor.xmlrpc +from erp5.util.testnode.SlapOSControler import SlapOSControler +from erp5.util.testnode.ProcessManager import ProcessManager + + +# Utility functions +def findFreeTCPPort(ip=''): + """Find a free TCP port to listen to. + """ + family = socket.AF_INET6 if ':' in ip else socket.AF_INET + with closing(socket.socket(family, socket.SOCK_STREAM)) as s: + s.bind((ip, 0)) + return s.getsockname()[1] + + +# TODO: +# - allow requesting multiple instances ? + +class SlapOSInstanceTestCase(unittest.TestCase): + """Install one slapos instance. + + This test case install software(s) and request one instance during `setUpClass` + and destroy the instance during `tearDownClass`. + + Software Release URL, Instance Software Type and Instance Parameters can be defined + on the class. + + All tests from the test class will run with the same instance. + + The following class attributes are available: + + * `computer_partition`: the `slapos.core.XXX` computer partition instance. + + * `computer_partition_root_path`: the path of the instance root directory, + + """ + + # Methods to be defined by subclasses. + @classmethod + def getSoftwareURLList(cls): + """Return URL of software releases to install. + + To be defined by subclasses. + """ + raise NotImplementedError() + + @classmethod + def getInstanceParameterDict(cls): + """Return instance parameters + + To be defined by subclasses if they need to request instance with specific + parameters. + """ + return {} + + @classmethod + def getInstanceSoftwareType(cls): + """Return software type for instance, default "default" + + To be defined by subclasses if they need to request instance with specific + software type. + """ + return "default" + + # Utility methods. + def getSupervisorRPCServer(self): + """Returns a XML-RPC connection to the supervisor used by slapos node + + Refer to http://supervisord.org/api.html for details of available methods. + """ + # xmlrpc over unix socket https://stackoverflow.com/a/11746051/7294664 + return xmlrpclib.ServerProxy( + 'http://slapos-supervisor', + transport=supervisor.xmlrpc.SupervisorTransport( + None, + None, + # XXX hardcoded socket path + serverurl="unix://{working_directory}/inst/supervisord.socket".format( + **self.config))) + + # Unittest methods + @classmethod + def setUpClass(cls): + """Setup the class, build software and request an instance. + + If you have to override this method, do not forget to call this method on + parent class. + """ + try: + cls.setUpWorkingDirectory() + cls.setUpConfig() + cls.setUpSlapOSController() + + cls.runSoftwareRelease() + # XXX instead of "runSoftwareRelease", it would be better to be closer to slapos usage: + # cls.supplySoftwares() + # cls.installSoftwares() + + cls.runComputerPartition() + # XXX instead of "runComputerPartition", it would be better to be closer to slapos usage: + # cls.requestInstances() + # cls.createInstances() + # cls.requestInstances() + + except BaseException: + cls.stopSlapOSProcesses() + cls.setUp = lambda self: self.fail('Setup Class failed.') + raise + + @classmethod + def tearDownClass(cls): + """Tear down class, stop the processes and destroy instance. + """ + cls.stopSlapOSProcesses() + + # Implementation + @classmethod + def stopSlapOSProcesses(cls): + if hasattr(cls, '_process_manager'): + cls._process_manager.killPreviousRun() + + @classmethod + def setUpWorkingDirectory(cls): + """Initialise the directories""" + cls.working_directory = os.environ.get( + 'SLAPOS_TEST_WORKING_DIR', + os.path.join(os.path.dirname(__file__), '.slapos')) + # To prevent error: Cannot open an HTTP server: socket.error reported + # AF_UNIX path too long This `working_directory` should not be too deep. + # Socket path is 108 char max on linux + # https://github.com/torvalds/linux/blob/3848ec5/net/unix/af_unix.c#L234-L238 + # Supervisord socket name contains the pid number, which is why we add + # .xxxxxxx in this check. + if len(cls.working_directory + '/inst/supervisord.socket.xxxxxxx') > 108: + raise RuntimeError('working directory ( {} ) is too deep, try setting ' + 'SLAPOS_TEST_WORKING_DIR'.format(cls.working_directory)) + + if not os.path.exists(cls.working_directory): + os.mkdir(cls.working_directory) + + @classmethod + def setUpConfig(cls): + """Create slapos configuration""" + cls.config = { + "working_directory": cls.working_directory, + "slapos_directory": cls.working_directory, + "log_directory": cls.working_directory, + "computer_id": 'slapos.test', # XXX + 'proxy_database': os.path.join(cls.working_directory, 'proxy.db'), + 'partition_reference': cls.__name__, + # "proper" slapos command must be in $PATH + 'slapos_binary': 'slapos', + } + # Some tests are expecting that local IP is not set to 127.0.0.1 + ipv4_address = os.environ.get('LOCAL_IPV4', '127.0.1.1') + ipv6_address = os.environ['GLOBAL_IPV6'] + + cls.config['proxy_host'] = cls.config['ipv4_address'] = ipv4_address + cls.config['ipv6_address'] = ipv6_address + cls.config['proxy_port'] = findFreeTCPPort(ipv4_address) + cls.config['master_url'] = 'http://{proxy_host}:{proxy_port}'.format( + **cls.config) + + @classmethod + def setUpSlapOSController(cls): + """Create the a "slapos controller" and supply softwares from `getSoftwareURLList`. + + This is equivalent to: + + slapos proxy start + for sr in getSoftwareURLList; do + slapos supply $SR $COMP + done + """ + cls._process_manager = ProcessManager() + + # XXX this code is copied from testnode code + cls.slapos_controler = SlapOSControler( + cls.working_directory, + cls.config + ) + + slapproxy_log = os.path.join(cls.config['log_directory'], 'slapproxy.log') + logger = logging.getLogger(__name__) + logger.debug('Configured slapproxy log to %r', slapproxy_log) + + cls.software_url_list = cls.getSoftwareURLList() + cls.slapos_controler.initializeSlapOSControler( + slapproxy_log=slapproxy_log, + process_manager=cls._process_manager, + reset_software=False, + software_path_list=cls.software_url_list) + + # XXX we should check *earlier* if that pidfile exist and if supervisord + # process still running, because if developer started supervisord (or bugs?) + # then another supervisord will start and starting services a second time + # will fail. + cls._process_manager.supervisord_pid_file = os.path.join( + cls.slapos_controler.instance_root, 'var', 'run', 'supervisord.pid') + + @classmethod + def runSoftwareRelease(cls): + """Run all the software releases that were supplied before. + + This is the equivalent of `slapos node software`. + + The tests will be marked file if software building fail. + """ + logger = logging.getLogger() + logger.level = logging.DEBUG + stream = StringIO.StringIO() + stream_handler = logging.StreamHandler(stream) + logger.addHandler(stream_handler) + + try: + cls.software_status_dict = cls.slapos_controler.runSoftwareRelease( + cls.config, environment=os.environ) + stream.seek(0) + stream.flush() + message = ''.join(stream.readlines()[-100:]) + assert cls.software_status_dict['status_code'] == 0, message + finally: + logger.removeHandler(stream_handler) + del stream + + + @classmethod + def runComputerPartition(cls): + """Instanciate the software. + + This is the equivalent of doing: + + slapos request --type=getInstanceSoftwareType --parameters=getInstanceParameterDict + slapos node instance + + and return the slapos request instance parameters. + + This can be called by tests to simulate re-request with different parameters. + """ + logger = logging.getLogger() + logger.level = logging.DEBUG + stream = StringIO.StringIO() + stream_handler = logging.StreamHandler(stream) + logger.addHandler(stream_handler) + + if cls.getInstanceSoftwareType() != 'default': + raise NotImplementedError + + instance_parameter_dict = cls.getInstanceParameterDict() + try: + cls.instance_status_dict = cls.slapos_controler.runComputerPartition( + cls.config, + cluster_configuration=instance_parameter_dict, + environment=os.environ) + stream.seek(0) + stream.flush() + message = ''.join(stream.readlines()[-100:]) + assert cls.instance_status_dict['status_code'] == 0, message + finally: + logger.removeHandler(stream_handler) + del stream + + # FIXME: similar to test node, only one (root) partition is really + # supported for now. + computer_partition_list = [] + for i in range(len(cls.software_url_list)): + computer_partition_list.append( + cls.slapos_controler.slap.registerOpenOrder().request( + cls.software_url_list[i], + # This is how testnode's SlapOSControler name created partitions + partition_reference='testing partition {i}'.format( + i=i, **cls.config), + partition_parameter_kw=instance_parameter_dict)) + + # expose some class attributes so that tests can use them: + # the ComputerPartition instances, to getInstanceParameterDict + cls.computer_partition = computer_partition_list[0] + + # the path of the instance on the filesystem, for low level inspection + cls.computer_partition_root_path = os.path.join( + cls.config['working_directory'], + 'inst', + cls.computer_partition.getId()) + + +