Re-architected the test suite.

Changed how environment variables are set in the Docker container.
master
Pacman Ghost 4 years ago
parent 92ccfdccf9
commit 02d1aeb408
  1. 4
      .pylintrc
  2. 14
      Dockerfile
  3. 95
      conftest.py
  4. 4
      docker/config/debug.cfg
  5. 1
      docker/config/site.cfg
  6. 4
      docker/run.sh
  7. 1
      requirements-dev.txt
  8. 193
      run-container.sh
  9. 5
      vasl_templates/main.py
  10. 20
      vasl_templates/tools/webdriver_stress_test.py
  11. 133
      vasl_templates/webapp/__init__.py
  12. 10
      vasl_templates/webapp/config/debug.cfg.example
  13. 2
      vasl_templates/webapp/config/site.cfg.example
  14. 3
      vasl_templates/webapp/downloads.py
  15. 3
      vasl_templates/webapp/files.py
  16. 78
      vasl_templates/webapp/main.py
  17. 95
      vasl_templates/webapp/run_server.py
  18. 45
      vasl_templates/webapp/testing.py
  19. 17
      vasl_templates/webapp/tests/compile-proto.sh
  20. 227
      vasl_templates/webapp/tests/control_tests.py
  21. 536
      vasl_templates/webapp/tests/control_tests_servicer.py
  22. BIN
      vasl_templates/webapp/tests/fixtures/vasl-extensions/real/BFP_v403.vmdx
  23. BIN
      vasl_templates/webapp/tests/fixtures/vasl-extensions/real/kgs-v1.1.mdx
  24. 32
      vasl_templates/webapp/tests/proto/__init__.py
  25. 164
      vasl_templates/webapp/tests/proto/control_tests.proto
  26. 7
      vasl_templates/webapp/tests/proto/delete_app_config_val_request.py
  27. 7
      vasl_templates/webapp/tests/proto/dump_vsav_request.py
  28. 1554
      vasl_templates/webapp/tests/proto/generated/control_tests_pb2.py
  29. 898
      vasl_templates/webapp/tests/proto/generated/control_tests_pb2_grpc.py
  30. 7
      vasl_templates/webapp/tests/proto/get_vasl_pieces_request.py
  31. 7
      vasl_templates/webapp/tests/proto/save_temp_file_request.py
  32. 14
      vasl_templates/webapp/tests/proto/set_app_config_val_request.py
  33. 7
      vasl_templates/webapp/tests/proto/set_asa_scenario_index_request.py
  34. 10
      vasl_templates/webapp/tests/proto/set_data_dir_request.py
  35. 7
      vasl_templates/webapp/tests/proto/set_default_scenario_request.py
  36. 21
      vasl_templates/webapp/tests/proto/set_default_template_pack_request.py
  37. 7
      vasl_templates/webapp/tests/proto/set_roar_scenario_index_request.py
  38. 7
      vasl_templates/webapp/tests/proto/set_user_files_dir_request.py
  39. 7
      vasl_templates/webapp/tests/proto/set_vasl_extn_info_dir_request.py
  40. 13
      vasl_templates/webapp/tests/proto/set_vasl_version_request.py
  41. 7
      vasl_templates/webapp/tests/proto/set_vassal_version_request.py
  42. 10
      vasl_templates/webapp/tests/proto/set_veh_ord_notes_dir_request.py
  43. 44
      vasl_templates/webapp/tests/proto/utils.py
  44. 373
      vasl_templates/webapp/tests/remote.py
  45. 74
      vasl_templates/webapp/tests/test_capabilities.py
  46. 22
      vasl_templates/webapp/tests/test_comments.py
  47. 125
      vasl_templates/webapp/tests/test_counters.py
  48. 5
      vasl_templates/webapp/tests/test_default_scenario.py
  49. 4
      vasl_templates/webapp/tests/test_dirty_scenario_checks.py
  50. 5
      vasl_templates/webapp/tests/test_extras_templates.py
  51. 21
      vasl_templates/webapp/tests/test_files.py
  52. 6
      vasl_templates/webapp/tests/test_jshint.py
  53. 82
      vasl_templates/webapp/tests/test_lfa.py
  54. 14
      vasl_templates/webapp/tests/test_national_capabilities.py
  55. 3
      vasl_templates/webapp/tests/test_ob.py
  56. 47
      vasl_templates/webapp/tests/test_online_images.py
  57. 4
      vasl_templates/webapp/tests/test_scenario_persistence.py
  58. 31
      vasl_templates/webapp/tests/test_scenario_search.py
  59. 23
      vasl_templates/webapp/tests/test_snippets.py
  60. 31
      vasl_templates/webapp/tests/test_template_packs.py
  61. 102
      vasl_templates/webapp/tests/test_user_settings.py
  62. 169
      vasl_templates/webapp/tests/test_vasl_extensions.py
  63. 321
      vasl_templates/webapp/tests/test_vassal.py
  64. 60
      vasl_templates/webapp/tests/test_vehicles_ordnance.py
  65. 88
      vasl_templates/webapp/tests/test_vo_notes.py
  66. 20
      vasl_templates/webapp/tests/test_vo_reports.py
  67. 37
      vasl_templates/webapp/tests/utils.py
  68. 14
      vasl_templates/webapp/utils.py
  69. 24
      vasl_templates/webapp/vasl_mod.py
  70. 10
      vasl_templates/webapp/vassal.py
  71. 2
      vasl_templates/webapp/vo.py
  72. 3
      vasl_templates/webapp/vo_notes.py

@ -7,7 +7,7 @@ extension-pkg-whitelist=PyQt5
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
ignore=generated
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
@ -18,7 +18,7 @@ ignore-patterns=
#init-hook=
# Use multiple processes to speed up Pylint.
jobs=1
jobs=4
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.

@ -24,18 +24,19 @@ RUN url=$( curl -s https://api.github.com/repos/mozilla/geckodriver/releases/lat
curl -sL "$url" \
| tar -C /usr/bin/ -xz
# clean up
RUN dnf clean all
# install the application requirements
WORKDIR /app
COPY requirements.txt requirements-dev.txt ./
RUN pip3 install -r requirements.txt
ARG ENABLE_TESTS
RUN if [ "$ENABLE_TESTS" ]; then \
ARG CONTROL_TESTS_PORT
RUN if [ -n "$CONTROL_TESTS_PORT" ]; then \
dnf install -y gcc-c++ python3-devel && \
pip3 install -r requirements-dev.txt \
; fi
# clean up
RUN dnf clean all
# install the application
COPY vasl_templates/webapp/ ./vasl_templates/webapp/
COPY vassal-shim/release/vassal-shim.jar ./vassal-shim/release/
@ -44,9 +45,6 @@ RUN pip3 install --editable .
# install the config files
COPY docker/config/ ./vasl_templates/webapp/config/
RUN if [ "$ENABLE_TESTS" ]; then \
echo "ENABLE_REMOTE_TEST_CONTROL = 1" >>vasl_templates/webapp/config/debug.cfg \
; fi
# create a new user
RUN useradd --create-home app

@ -2,6 +2,8 @@
import os
import threading
import json
import re
import logging
import tempfile
import urllib.request
@ -11,8 +13,8 @@ import pytest
from flask import url_for
from vasl_templates.webapp import app
app.testing = True
from vasl_templates.webapp.tests import utils
from vasl_templates.webapp.tests.control_tests import ControlTests
FLASK_WEBAPP_PORT = 5011
@ -25,7 +27,7 @@ def pytest_addoption( parser ):
# add test options
parser.addoption(
"--server-url", action="store", dest="server_url", default=None,
"--webapp", action="store", dest="webapp_url", default=None,
help="Webapp server to test against."
)
# NOTE: Chrome seems to be ~15% faster than Firefox, headless ~5% faster than headful.
@ -48,31 +50,6 @@ def pytest_addoption( parser ):
help="Run a shorter version of the test suite."
)
# NOTE: Some tests require the VASL module file(s). We don't want to put these into source control,
# so we provide this option to allow the caller to specify where they live.
parser.addoption(
"--vasl-mods", action="store", dest="vasl_mods", default=None,
help="Directory containing the VASL .vmod file(s)."
)
parser.addoption(
"--vasl-extensions", action="store", dest="vasl_extensions", default=None,
help="Directory containing the VASL extensions."
)
# NOTE: Some tests require VASSAL to be installed. This option allows the caller to specify
# where it is (multiple installations can be placed in sub-directories).
parser.addoption(
"--vassal", action="store", dest="vassal", default=None,
help="Directory containing VASSAL installation(s)."
)
# NOTE: Some tests require Chapter H vehicle/ordnance notes. This is copyrighted material,
# so it is kept in a private repo.
parser.addoption(
"--vo-notes", action="store", dest="vo_notes", default=None,
help="Directory containing Chapter H vehicle/ordnance notes and test results."
)
# NOTE: It's not good to have the code run differently to how it will normally,
# but using the clipboard to retrieve snippets causes more trouble than it's worth :-/
# since any kind of clipboard activity while the tests are running could cause them to fail
@ -84,13 +61,34 @@ def pytest_addoption( parser ):
# ---------------------------------------------------------------------
@pytest.fixture( scope="session" )
_webapp = None
@pytest.fixture( scope="function" )
def webapp():
"""Launch the webapp."""
# get the global webapp fixture
global _webapp
if _webapp is None:
_webapp = _make_webapp()
# reset the remote webapp server
_webapp.control_tests.start_tests()
# return the webapp to the caller
yield _webapp
# reset the remote webapp server
_webapp.control_tests.end_tests()
def _make_webapp():
"""Create the global webapp fixture."""
# initialize
server_url = pytest.config.option.server_url #pylint: disable=no-member
app.base_url = server_url if server_url else "http://localhost:{}".format(FLASK_WEBAPP_PORT)
webapp_url = pytest.config.option.webapp_url #pylint: disable=no-member
if webapp_url and not webapp_url.startswith( "http://" ):
webapp_url = "http://" + webapp_url
app.base_url = webapp_url if webapp_url else "http://localhost:{}".format( FLASK_WEBAPP_PORT )
logging.disable( logging.CRITICAL )
# initialize
@ -119,10 +117,19 @@ def webapp():
app.url_for = make_webapp_url
# check if we need to start a local webapp server
if not server_url:
if not webapp_url:
# yup - make it so
# NOTE: We run the server thread as a daemon so that it won't prevent the tests from finishing
# when they're done. We used to call $/shutdown after yielding the webapp fixture, but when
# we changed it from being per-session to per-function, we can no longer do that.
# This means that the webapp doesn't get a chance to shutdown properly (in particular,
# clean up the gRPC service), but since we send an EndTests message at the of each test,
# the remote server gets a chance to clean up then. It's not perfect (e.g. if the tests fail
# or otherwise finish eearly before they get a chance to send the EndTests message), but
# we can live with it.
thread = threading.Thread(
target = lambda: app.run( host="0.0.0.0", port=FLASK_WEBAPP_PORT, use_reloader=False )
target = lambda: app.run( host="0.0.0.0", port=FLASK_WEBAPP_PORT, use_reloader=False ),
daemon = True
)
thread.start()
# wait for the server to start up
@ -138,13 +145,23 @@ def webapp():
assert False, "Unexpected exception: {}".format(ex)
utils.wait_for( 5, is_ready )
# return the server to the caller
yield app
# shutdown the local webapp server
if not server_url:
urllib.request.urlopen( app.url_for("shutdown") ).read()
thread.join()
# set up control of the remote webapp server
try:
resp = json.load(
urllib.request.urlopen( app.url_for( "get_control_tests" ) )
)
except urllib.error.HTTPError as ex:
if ex.code == 404:
raise RuntimeError( "Can't get the test control port - has remote test control been enabled?" )
raise
port_no = resp.get( "port" )
if not port_no:
raise RuntimeError( "The webapp server is not running the test control service." )
mo = re.search( r"^http://(.+):\d+$", app.base_url )
addr = "{}:{}".format( mo.group(1), port_no )
app.control_tests = ControlTests( addr )
return app
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

@ -1,5 +1,5 @@
[Debug]
; NOTE: These need to be mapped in if you want to run the test suite against a container.
TEST_VASSAL_ENGINES = /test-data/vassal/
TEST_VASL_MODS = /test-data/vasl-vmods/
TEST_VASL_EXTNS_DIR = /test-data/vasl-extensions/
TEST_VASL_MODS = /test-data/vasl-mods/

@ -1,6 +1,5 @@
[Site Config]
FLASK_HOST = 0.0.0.0
IS_CONTAINER = 1
JAVA_PATH = /usr/bin/jdk-15.0.1/bin/java

@ -6,4 +6,6 @@ export DISPLAY=:10.0
Xvfb :10 -ac 1>/tmp/xvfb.log 2>/tmp/xvfb.err &
# run the webapp server
python3 /app/vasl_templates/webapp/run_server.py
python3 /app/vasl_templates/webapp/run_server.py \
--addr 0.0.0.0 \
--force-init-delay 30

@ -1,4 +1,5 @@
pytest==3.6.0
grpcio-tools==1.33.2
tabulate==0.8.2
lxml==4.2.4
pylint==1.9.2

@ -10,19 +10,25 @@ function print_help {
-p --port Web server port number.
--vassal VASSAL installation directory.
-v --vasl-vmod Path to the VASL .vmod file.
-v --vasl Path to the VASL module file (.vmod ).
-e --vasl-extensions Path to the VASL extensions directory.
--boards Path to the VASL boards.
--chapter-h Path to the Chapter H notes directory.
--user-files Path to the user files directory.
-k --template-pack Path to a user-defined template pack.
-t --tag Docker tag.
-t --tag Docker image tag.
--name Docker container name.
-d --detach Detach from the container and let it run in the background.
--no-build Launch the container as-is (i.e. without rebuilding it first).
--build-network Docker network to use when building the container.
--no-build Launch the container as-is (i.e. without rebuilding the image first).
--build-network Docker network to use when building the image.
--run-network Docker network to use when running the container.
Options for the test suite:
--control-tests-port Remote test control port number.
--test-data-vassal Directory containing VASSAL releases.
--test-data-vasl-mods Directory containing VASL modules.
NOTE: If the port the webapp server is listening on *inside* the container is different
to the port exposed *outside* the container, webdriver image generation (e.g. Shift-Click
on a snippet button, or Chapter H content as images) may not work properly. This is because
@ -39,32 +45,29 @@ EOM
# initialize
cd `dirname "$0"`
PORT=5010
VASSAL_LOCAL=
VASSAL=
VASL_MOD_LOCAL=
VASL_MOD=
VASL_BOARDS_LOCAL=
VASL_BOARDS=
VASL_EXTNS_LOCAL=
VASL_EXTNS=
CHAPTER_H_NOTES_LOCAL=
VASL_BOARDS=
CHAPTER_H_NOTES=
TEMPLATE_PACK_LOCAL=
TEMPLATE_PACK=
USER_FILES_LOCAL=
USER_FILES=
TAG=latest
TEMPLATE_PACK=
IMAGE_TAG=latest
CONTAINER_NAME=vasl-templates
DETACH=
NO_BUILD=
BUILD_NETWORK=
RUN_NETWORK=
CONTROL_TESTS_PORT=
TEST_DATA_VASSAL=
TEST_DATA_VASL_MODS=
# parse the command-line arguments
if [ $# -eq 0 ]; then
print_help
exit 0
fi
params="$(getopt -o p:v:e:k:t:d -l port:,vassal:,vasl-vmod:,vasl-extensions:,boards:,chapter-h:,user-files:,template-pack:,tag:,detach,no-build,build-network:,run-network:,help --name "$0" -- "$@")"
params="$(getopt -o p:v:e:k:t:d -l port:,control-tests-port:,vassal:,vasl:,vasl-extensions:,boards:,chapter-h:,template-pack:,user-files:,tag:,name:,detach,no-build,build-network:,run-network:,test-data-vassal:,test-data-vasl-mods:,help --name "$0" -- "$@")"
if [ $? -ne 0 ]; then exit 1; fi
eval set -- "$params"
while true; do
@ -73,28 +76,31 @@ while true; do
PORT=$2
shift 2 ;;
--vassal)
VASSAL_LOCAL=$2
VASSAL=$2
shift 2 ;;
-v | --vasl-vmod)
VASL_MOD_LOCAL=$2
-v | --vasl)
VASL_MOD=$2
shift 2 ;;
-e | --vasl-extensions)
VASL_EXTNS_LOCAL=$2
VASL_EXTNS=$2
shift 2 ;;
--boards)
VASL_BOARDS_LOCAL=$2
VASL_BOARDS=$2
shift 2 ;;
--chapter-h)
CHAPTER_H_NOTES_LOCAL=$2
CHAPTER_H_NOTES=$2
shift 2 ;;
--user-files)
USER_FILES_LOCAL=$2
USER_FILES=$2
shift 2 ;;
-k | --template-pack)
TEMPLATE_PACK_LOCAL=$2
TEMPLATE_PACK=$2
shift 2 ;;
-t | --tag)
TAG=$2
IMAGE_TAG=$2
shift 2 ;;
--name)
CONTAINER_NAME=$2
shift 2 ;;
-d | --detach )
DETACH=--detach
@ -110,6 +116,17 @@ while true; do
--run-network )
RUN_NETWORK="--network $2"
shift 2 ;;
--control-tests-port)
CONTROL_TESTS_PORT=$2
shift 2 ;;
--test-data-vassal )
target=`readlink -f "$2"`
TEST_DATA_VASSAL="--volume $target:/test-data/vassal/"
shift 2 ;;
--test-data-vasl-mods )
target=`readlink -f "$2"`
TEST_DATA_VASL_MODS="--volume $target:/test-data/vasl-mods/"
shift 2 ;;
--help )
print_help
exit 0 ;;
@ -121,102 +138,114 @@ while true; do
done
# check if a VASSAL directory has been specified
if [ -n "$VASSAL_LOCAL" ]; then
if [ ! -d "$VASSAL_LOCAL" ]; then
echo "Can't find the VASSAL directory: $VASSAL_LOCAL"
if [ -n "$VASSAL" ]; then
if [ ! -d "$VASSAL" ]; then
echo "Can't find the VASSAL directory: $VASSAL"
exit 1
fi
VASSAL=/data/vassal/
VASSAL_VOLUME="--volume `readlink -f "$VASSAL_LOCAL"`:$VASSAL"
VASSAL_ENV="--env VASSAL_DIR=$VASSAL"
target=/data/vassal/
VASSAL_VOLUME="--volume `readlink -f "$VASSAL"`:$target"
VASSAL_ENV="--env VASSAL_DIR=$target"
fi
# check if a VASL .vmod file has been specified
if [ -n "$VASL_MOD_LOCAL" ]; then
if [ ! -f "$VASL_MOD_LOCAL" ]; then
echo "Can't find the VASL .vmod file: $VASL_MOD_LOCAL"
# check if a VASL module file has been specified
if [ -n "$VASL_MOD" ]; then
if [ ! -f "$VASL_MOD" ]; then
echo "Can't find the VASL .vmod file: $VASL_MOD"
exit 1
fi
VASL_MOD=/data/vasl.vmod
VASL_MOD_VOLUME="--volume `readlink -f "$VASL_MOD_LOCAL"`:$VASL_MOD"
VASL_MOD_ENV="--env VASL_MOD=$VASL_MOD"
target=/data/vasl.vmod
VASL_MOD_VOLUME="--volume `readlink -f "$VASL_MOD"`:$target"
VASL_MOD_ENV="--env VASL_MOD=$target"
fi
# check if a VASL boards directory has been specified
if [ -n "$VASL_BOARDS_LOCAL" ]; then
if [ ! -d "$VASL_BOARDS_LOCAL" ]; then
echo "Can't find the VASL boards directory: $VASL_BOARDS_LOCAL"
# check if a VASL extensions directory has been specified
if [ -n "$VASL_EXTNS" ]; then
if [ ! -d "$VASL_EXTNS" ]; then
echo "Can't find the VASL extensions directory: $VASL_EXTNS"
exit 1
fi
VASL_BOARDS=/data/boards/
VASL_BOARDS_VOLUME="--volume `readlink -f "$VASL_BOARDS_LOCAL"`:$VASL_BOARDS"
VASL_BOARDS_ENV="--env VASL_BOARDS_DIR=$VASL_BOARDS"
target=/data/vasl-extensions/
VASL_EXTNS_VOLUME="--volume `readlink -f "$VASL_EXTNS"`:$target"
VASL_EXTNS_ENV="--env VASL_EXTNS_DIR=$target"
fi
# check if a VASL extensions directory has been specified
if [ -n "$VASL_EXTNS_LOCAL" ]; then
if [ ! -d "$VASL_EXTNS_LOCAL" ]; then
echo "Can't find the VASL extensions directory: $_EXTNS_DIR_LOCAL"
# check if a VASL boards directory has been specified
if [ -n "$VASL_BOARDS" ]; then
if [ ! -d "$VASL_BOARDS" ]; then
echo "Can't find the VASL boards directory: $VASL_BOARDS"
exit 1
fi
VASL_EXTNS=/data/vasl-extensions/
VASL_EXTNS_VOLUME="--volume `readlink -f "$VASL_EXTNS_LOCAL"`:$VASL_EXTNS"
VASL_EXTNS_ENV="--env VASL_EXTNS_DIR=$VASL_EXTNS"
target=/data/boards/
VASL_BOARDS_VOLUME="--volume `readlink -f "$VASL_BOARDS"`:$target"
VASL_BOARDS_ENV="--env BOARDS_DIR=$target"
fi
# check if a Chapter H notes directory has been specified
if [ -n "$CHAPTER_H_NOTES_LOCAL" ]; then
if [ ! -d "$CHAPTER_H_NOTES_LOCAL" ]; then
echo "Can't find the Chapter H notes directory: $CHAPTER_H_NOTES_LOCAL"
if [ -n "$CHAPTER_H_NOTES" ]; then
if [ ! -d "$CHAPTER_H_NOTES" ]; then
echo "Can't find the Chapter H notes directory: $CHAPTER_H_NOTES"
exit 1
fi
CHAPTER_H_NOTES=/data/chapter-h-notes/
CHAPTER_H_NOTES_VOLUME="--volume `readlink -f "$CHAPTER_H_NOTES_LOCAL"`:$CHAPTER_H_NOTES"
CHAPTER_H_NOTES_ENV="--env CHAPTER_H_NOTES_DIR=$CHAPTER_H_NOTES"
target=/data/chapter-h-notes/
CHAPTER_H_NOTES_VOLUME="--volume `readlink -f "$CHAPTER_H_NOTES"`:$target"
CHAPTER_H_NOTES_ENV="--env CHAPTER_H_NOTES_DIR=$target"
fi
# check if a template pack has been specified
if [ -n "$TEMPLATE_PACK_LOCAL" ]; then
# NOTE: The template pack can either be a file (ZIP) or a directory.
if ! ls "$TEMPLATE_PACK_LOCAL" >/dev/null 2>&1 ; then
echo "Can't find the template pack: $TEMPLATE_PACK_LOCAL"
# check if a user files directory has been specified
if [ -n "$USER_FILES" ]; then
if [ ! -d "$USER_FILES" ]; then
echo "Can't find the user files directory: $USER_FILES"
exit 1
fi
TEMPLATE_PACK=/data/template-pack
TEMPLATE_PACK_VOLUME="--volume `readlink -f "$TEMPLATE_PACK_LOCAL"`:$TEMPLATE_PACK"
TEMPLATE_PACK_ENV="--env DEFAULT_TEMPLATE_PACK=$TEMPLATE_PACK"
target=/data/user-files/
USER_FILES_VOLUME="--volume `readlink -f "$USER_FILES"`:$target"
USER_FILES_ENV="--env USER_FILES_DIR=$target"
fi
# check if a user files directory has been specified
if [ -n "$USER_FILES_LOCAL" ]; then
if [ ! -d "$USER_FILES_LOCAL" ]; then
echo "Can't find the user files directory: $USER_FILES_LOCAL"
# check if a template pack has been specified
if [ -n "$TEMPLATE_PACK" ]; then
# NOTE: The template pack can either be a file (ZIP) or a directory.
if ! ls "$TEMPLATE_PACK" >/dev/null 2>&1 ; then
echo "Can't find the template pack: $TEMPLATE_PACK"
exit 1
fi
USER_FILES=/data/user-files/
USER_FILES_VOLUME="--volume `readlink -f "$USER_FILES_LOCAL"`:$USER_FILES"
USER_FILES_ENV="--env USER_FILES_DIR=$USER_FILES"
target=/data/template-pack
TEMPLATE_PACK_VOLUME="--volume `readlink -f "$TEMPLATE_PACK"`:$target"
TEMPLATE_PACK_ENV="--env DEFAULT_TEMPLATE_PACK=$target"
fi
# check if testing has been enabled
if [ -n "$CONTROL_TESTS_PORT" ]; then
CONTROL_TESTS_PORT_BUILD="--build-arg CONTROL_TESTS_PORT=$CONTROL_TESTS_PORT"
CONTROL_TESTS_PORT_RUN="--env CONTROL_TESTS_PORT=$CONTROL_TESTS_PORT --publish $CONTROL_TESTS_PORT:$CONTROL_TESTS_PORT"
fi
# build the container
# build the image
if [ -z "$NO_BUILD" ]; then
echo Building the \"$TAG\" container...
docker build $BUILD_NETWORK --tag vasl-templates:$TAG . 2>&1 \
| sed -e 's/^/ /'
echo Building the \"$IMAGE_TAG\" image...
docker build \
--tag vasl-templates:$IMAGE_TAG \
$CONTROL_TESTS_PORT_BUILD \
$BUILD_NETWORK \
. 2>&1 \
| sed -e 's/^/ /'
if [ ${PIPESTATUS[0]} -ne 0 ]; then exit 10 ; fi
echo
fi
# launch the container
echo Launching the \"$TAG\" container...
echo Launching the \"$IMAGE_TAG\" image as \"$CONTAINER_NAME\"...
docker run \
--name $CONTAINER_NAME \
--publish $PORT:5010 \
--name vasl-templates \
$VASSAL_VOLUME $VASL_MOD_VOLUME $VASL_BOARDS_VOLUME $VASL_EXTNS_VOLUME $CHAPTER_H_NOTES_VOLUME $USER_FILES_VOLUME $TEMPLATE_PACK_VOLUME \
$VASSAL_ENV $VASL_MOD_ENV $VASL_BOARDS_ENV $VASL_EXTNS_ENV $CHAPTER_H_NOTES_ENV $USER_FILES_ENV $TEMPLATE_PACK_ENV \
$CONTROL_TESTS_PORT_RUN \
$VASSAL_VOLUME $VASL_MOD_VOLUME $VASL_EXTNS_VOLUME $VASL_BOARDS_VOLUME $CHAPTER_H_NOTES_VOLUME $TEMPLATE_PACK_VOLUME $USER_FILES_VOLUME \
$VASSAL_ENV $VASL_MOD_ENV $VASL_EXTNS_ENV $VASL_BOARDS_ENV $CHAPTER_H_NOTES_ENV $TEMPLATE_PACK_ENV $USER_FILES_ENV \
$RUN_NETWORK $DETACH \
$TEST_DATA_VASSAL $TEST_DATA_VASL_MODS \
-it --rm \
vasl-templates:$TAG \
vasl-templates:$IMAGE_TAG \
2>&1 \
| sed -e 's/^/ /'
| sed -e 's/^/ /'
exit ${PIPESTATUS[0]}

@ -205,11 +205,6 @@ def _do_main( template_pack, default_scenario, remote_debugging, debug ): #pylin
main_window.show()
ret_code = qt_app.exec_()
# shutdown the webapp server
url = "http://localhost:{}/shutdown".format( port )
urllib.request.urlopen( url ).read()
thread.join()
return ret_code
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

@ -31,11 +31,11 @@ stats = defaultdict( lambda: [0,0] ) # nb: [ #runs, total elapsed time ]
# ---------------------------------------------------------------------
@click.command()
@click.option( "--server-url", default="http://localhost:5010", help="Webapp server URL." )
@click.option( "--webapp-url", default="http://localhost:5010", help="Webapp server URL." )
@click.option( "--snippet-images", default=1, help="Number of 'snippet image' threads to run." )
@click.option( "--update-vsav", default=1, help="Number of 'update VSAV' threads to run." )
@click.option( "--vsav","vsav_fname", help="VASL scenario file (.vsav) to be updated." )
def main( server_url, snippet_images, update_vsav, vsav_fname ):
def main( webapp_url, snippet_images, update_vsav, vsav_fname ):
"""Stress-test the shared WebDriver."""
# initialize
@ -52,13 +52,13 @@ def main( server_url, snippet_images, update_vsav, vsav_fname ):
threads.append( threading.Thread(
target = snippet_images_thread,
name = "snippet-images/{:02d}".format( 1+i ),
args = ( server_url, )
args = ( webapp_url, )
) )
for i in range(0,update_vsav):
threads.append( threading.Thread(
target = update_vsav_thread,
name = "update-vsav/{:02d}".format( 1+i ),
args = ( server_url, vsav_fname, vsav_data )
args = ( webapp_url, vsav_fname, vsav_data )
) )
# launch the test threads
@ -96,14 +96,14 @@ def main( server_url, snippet_images, update_vsav, vsav_fname ):
# ---------------------------------------------------------------------
def snippet_images_thread( server_url ):
def snippet_images_thread( webapp_url ):
"""Test generating snippet images."""
with WebDriver() as webdriver:
# initialize
webdriver = webdriver.driver
init_webapp( webdriver, server_url,
init_webapp( webdriver, webapp_url,
[ "snippet_image_persistence", "scenario_persistence" ]
)
@ -169,7 +169,7 @@ def snippet_images_thread( server_url ):
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def update_vsav_thread( server_url, vsav_fname, vsav_data ):
def update_vsav_thread( webapp_url, vsav_fname, vsav_data ):
"""Test updating VASL scenario files."""
# initialize
@ -179,7 +179,7 @@ def update_vsav_thread( server_url, vsav_fname, vsav_data ):
# initialize
webdriver = webdriver.driver
init_webapp( webdriver, server_url,
init_webapp( webdriver, webapp_url,
[ "vsav_persistence", "scenario_persistence" ]
)
@ -242,10 +242,10 @@ def log( fmt, *args, **kwargs ):
# ---------------------------------------------------------------------
def init_webapp( webdriver, server_url, options ):
def init_webapp( webdriver, webapp_url, options ):
"""Initialize the webapp."""
log( "Initializing the webapp." )
url = server_url + "?" + "&".join( "{}=1".format(opt) for opt in options )
url = webapp_url + "?" + "&".join( "{}=1".format(opt) for opt in options )
url += "&store_msgs=1" # nb: stop notification balloons from building up
webdriver.get( url )
wait_for( 5, lambda: find_child("#_page-loaded_",webdriver) is not None )

@ -4,46 +4,70 @@ import sys
import os
import signal
import threading
import time
import tempfile
import configparser
import logging
import logging.config
from flask import Flask
from flask import Flask, request
import yaml
from vasl_templates.webapp.config.constants import BASE_DIR
shutdown_event = threading.Event()
_LOCK_FNAME = os.path.join( tempfile.gettempdir(), "vasl-templates.lock" )
# ---------------------------------------------------------------------
def _on_startup():
_init_done = False
_init_lock = threading.Lock()
def _on_request():
"""Called before each request."""
# initialize the webapp on the first request, except for $/control-tests.
# NOTE: The test suite calls $/control-tests to find out which port the gRPC test control service
# is running on, which is nice since we don't need to configure both ends with a predefined port.
# However, we don't want this call to trigger initialization, since the tests will often want to
# configure the remote webapp before loading the main page.
if request.path == "/control-tests":
return
with _init_lock:
global _init_done
if not _init_done or (request.path == "/" and request.args.get("force-reinit")):
_init_webapp()
_init_done = True
def _init_webapp():
"""Do startup initialization."""
# NOTE: While this is generally called only once (before the first request), the test suite
# can force it be done again, since it wants to reconfigure the server to test different cases.
# start downloading files
# NOTE: We used to do this in the mainline code of __init__, so that we didn't have to wait
# for the first request before starting the download (useful if we are running as a standalone server).
# However, this means that the downloads start whenever we import this module e.g. for a stand-alone
# command-line tool :-/ Instead, we send a dummy request in run_server.py to trigger a call
# to this function.
from vasl_templates.webapp.downloads import DownloadedFile
threading.Thread( daemon=True,
target = DownloadedFile.download_files
).start()
if not _init_done:
from vasl_templates.webapp.downloads import DownloadedFile
threading.Thread( daemon=True,
target = DownloadedFile.download_files
).start()
# load the default template_pack
from vasl_templates.webapp.snippets import load_default_template_pack
load_default_template_pack()
# configure the VASL module
# NOTE: The Docker container configures this setting via an environment variable.
fname = app.config.get( "VASL_MOD", os.environ.get("VASL_MOD") )
if fname:
from vasl_templates.webapp.vasl_mod import set_vasl_mod #pylint: disable=cyclic-import
from vasl_templates.webapp.main import startup_msg_store #pylint: disable=cyclic-import
set_vasl_mod( fname, startup_msg_store )
fname = app.config.get( "VASL_MOD" )
from vasl_templates.webapp.vasl_mod import set_vasl_mod #pylint: disable=cyclic-import
from vasl_templates.webapp.main import startup_msg_store #pylint: disable=cyclic-import
set_vasl_mod( fname, startup_msg_store )
# load the vehicle/ordnance listings
from vasl_templates.webapp.vo import load_vo_listings #pylint: disable=cyclic-import
from vasl_templates.webapp.main import startup_msg_store #pylint: disable=cyclic-import
load_vo_listings( startup_msg_store )
# load the vehicle/ordnance notes
@ -65,19 +89,80 @@ def load_debug_config( fname ):
"""Configure the application."""
_load_config( fname, "Debug" )
def _set_config_from_env( key ):
"""Set an app config setting from an environment variable."""
val = os.environ.get( key )
if val:
app.config[ key ] = val
def _is_flask_child_process():
"""Check if we are the Flask child process."""
# NOTE: There are actually 3 possible cases:
# (*) Flask reloading is enabled:
# - we are the parent process (returns False)
# - we are the child process (returns True)
# (*) Flask reloading is disabled:
# - returns False
return os.environ.get( "WERKZEUG_RUN_MAIN" ) is not None
# ---------------------------------------------------------------------
def _on_sigint( signum, stack ): #pylint: disable=unused-argument
"""Clean up after a SIGINT."""
# FUDGE! Since we added gRPC test control, we want to shutdown properly and clean things up (e.g. temp files
# created by the gRPC service), but the Flask reloader complicates what we have to do here horribly :-(
# Since automatic reloading is a really nice feature to have, we try to handle things.
# If the Flask app is started with reloading enabled, it launches a child process to actually do the work,
# that is restarted when any of the monitored files change. It's easy for each process to figure out
# if it's the parent or child, but they need to synchronize their shutdown. Both processes get the SIGINT,
# but the parent can't just exit, since that will cause the child process to terminate, even if it hasn't
# finished shutting down i.e. the parent process needs to wait for the child process to finish shutting down
# before it can exit itself.
# Unfortunately, the way the child process is launched (see werkzeug._reloader.restart_with_reloader())
# means that there is no way for us to know what the child process is (and hence be able to wait for it),
# so the way the child process tells its parent that it has finished shutting down is via a lock file.
# NOTE: We always go through the shutdown process, regardless of whether we are the Flask parent or child process,
# because if Flask reloading is disabled, there will be only one process (that will look like it's the parent),
# and there doesn't seem to be any way to check if reloading is enabled or not. Note that if reloading is enabled,
# then doing shutdown in the parent process will be harmless, since it won't have done any real work (it's all done
# by the child process), and so there won't be anything to clean up.
# notify everyone that we're shutting down
shutdown_event.set()
# call any registered cleanup handlers
from vasl_templates.webapp import globvars #pylint: disable=cyclic-import
for handler in globvars.cleanup_handlers:
handler()
raise SystemExit()
if _is_flask_child_process():
# notify the parent process that we're done
os.unlink( _LOCK_FNAME )
else:
# we are the Flask parent process (so we wait for the child process to finish) or Flask reloading
# is disabled (and the wait below will end immediately, because the lock file was never created).
# NOTE: If, for whatever reason, the lock file doesn't get deleted, we give up waiting and exit anyway.
# This means that the child process might not get to finish cleaning up properly, but if it hasn't
# deleted the lock file, it was probably in trouble anyway.
for _ in range(0, 20):
# NOTE: os.path.isfile() and .exists() both return True even after the log file has gone!?!?
# Is somebody caching something somewhere? :-/
try:
open( _LOCK_FNAME, "r" )
except FileNotFoundError:
break
time.sleep( 0.1 )
raise SystemExit()
# ---------------------------------------------------------------------
# initialize Flask
app = Flask( __name__ )
if _is_flask_child_process():
# we are the Flask child process - create a lock file
open( _LOCK_FNAME, "w" ).close()
# set config defaults
# NOTE: These are defined here since they are used by both the back- and front-ends.
@ -102,6 +187,21 @@ _fname = os.path.join( config_dir, "debug.cfg" )
if os.path.isfile( _fname ) :
load_debug_config( _fname )
# load any config from environment variables (e.g. set in the Docker container)
# NOTE: We could add these settings to the container's site.cfg, so that they are always defined, and things
# would work (or not) depending on whether anything had been mapped to the endpoints. For example, if nothing
# had been mapped to /data/vassal/, we would not find a Vengine.jar and it would look like no VASSAL engine
# had been configured). However, requiring things to be explicitly turned on via an environment variable
# lets us issue better error message, such as "VASSAL has not been configured".
_set_config_from_env( "VASSAL_DIR" )
_set_config_from_env( "VASL_MOD" )
_set_config_from_env( "VASL_EXTNS_DIR" )
_set_config_from_env( "BOARDS_DIR" )
_set_config_from_env( "CHAPTER_H_NOTES_DIR" )
_set_config_from_env( "USER_FILES_DIR" )
# NOTE: The Docker container also sets DEFAULT_TEMPLATE_PACK, but we read it directly from
# the environment variable, since it is not something that is stored in app.config.
# initialize logging
_fname = os.path.join( config_dir, "logging.yaml" )
if os.path.isfile( _fname ):
@ -125,12 +225,9 @@ import vasl_templates.webapp.nat_caps #pylint: disable=cyclic-import
import vasl_templates.webapp.scenarios #pylint: disable=cyclic-import
import vasl_templates.webapp.downloads #pylint: disable=cyclic-import
import vasl_templates.webapp.lfa #pylint: disable=cyclic-import
if app.config.get( "ENABLE_REMOTE_TEST_CONTROL" ):
print( "*** WARNING: Remote test control enabled! ***" )
import vasl_templates.webapp.testing #pylint: disable=cyclic-import
# install our signal handler (must be done in the main thread)
signal.signal( signal.SIGINT, _on_sigint )
# register startup initialization
app.before_first_request( _on_startup )
app.before_request( _on_request )

@ -0,0 +1,10 @@
[Debug]
; Set this if you want to run the test suite (allows the webapp server to be controlled using gRPC).
; CONTROL_TESTS_PORT = -1
; Set this to a directory containing the VASSAL releases to run the test suite with.
; TEST_VASSAL_ENGINES = ...
; Set this to a directory containing the VASL modules (.vmod files) to run the test suite with.
; TEST_VASL_MODS = ...

@ -6,7 +6,7 @@ VASL_MOD = ...configure the VASL module (e.g. vasl-6.5.0.vmod)...
VASL_EXTNS_DIR = ...configured the VASL extensions directory...
BOARDS_DIR = ...configure the VASL boards directory...
; configure support prorams
; configure support params
; JAVA_PATH = ...configure the Java executable here (optional, must be in the PATH otherwise)...
WEBDRIVER_PATH = ...configure either geckodriver or chromedriver here...

@ -130,6 +130,9 @@ class DownloadedFile:
continue
# download the file
if app.config.get( "DISABLE_DOWNLOADED_FILES" ):
_logger.info( "Download disabled (%s): %s", df.key, url )
continue
_logger.info( "Downloading the %s file: %s", df.key, url )
try:
headers = { "Accept-Encoding": "gzip, deflate" }

@ -60,8 +60,7 @@ class FileServer:
@app.route( "/user/<path:path>" )
def get_user_file( path ):
"""Get a static file."""
# NOTE: The Docker container configures this setting via an environment variable.
dname = app.config.get( "USER_FILES_DIR", os.environ.get("USER_FILES_DIR") )
dname = app.config.get( "USER_FILES_DIR" )
if not dname:
abort( 404 )
if not os.path.isdir( dname ):

@ -1,14 +1,16 @@
""" Main webapp handlers. """
import os
import threading
import concurrent
import json
import uuid
import logging
from flask import request, render_template, jsonify, send_file, redirect, url_for, abort
from vasl_templates.webapp import app
from vasl_templates.webapp.utils import MsgStore
from vasl_templates.webapp import app, shutdown_event
from vasl_templates.webapp.utils import MsgStore, parse_int
import vasl_templates.webapp.config.constants
from vasl_templates.webapp.config.constants import BASE_DIR, DATA_DIR
from vasl_templates.webapp import globvars
@ -212,15 +214,71 @@ def get_default_scenario():
# ---------------------------------------------------------------------
_control_tests_port_no = None
@app.route( "/control-tests" )
def get_control_tests():
"""Return information about the remote test control service."""
def get_port():
"""Get the configured gRPC service port."""
# NOTE: The Docker container configures this setting via an environment variable.
return app.config.get( "CONTROL_TESTS_PORT", os.environ.get("CONTROL_TESTS_PORT") )
# check if the test control service should be made available
port_no = get_port()
if not port_no:
abort( 404 )
# check if we've already started the service
if not _control_tests_port_no:
# nope - make it so
print( "*** WARNING: Remote test control enabled! ***" )
started_event = threading.Event()
def run_service(): #pylint: disable=missing-docstring
import grpc
server = grpc.server( concurrent.futures.ThreadPoolExecutor( max_workers=1 ) )
from vasl_templates.webapp.tests.proto.generated.control_tests_pb2_grpc \
import add_ControlTestsServicer_to_server
from vasl_templates.webapp.tests.control_tests_servicer import ControlTestsServicer #pylint: disable=cyclic-import
servicer = ControlTestsServicer( app )
add_ControlTestsServicer_to_server( servicer, server )
port_no = parse_int( get_port(), -1 ) # nb: have to get this again?!
if port_no <= 0:
# NOTE: Requesting port 0 tells grpc to use any free port, which is usually OK, unless
# we're running inside a Docker container, in which case it needs to be pre-defined,
# so that the port can be mapped to an external port when the container is started.
port_no = 0
port_no = server.add_insecure_port( "[::]:{}".format( port_no ) )
logging.getLogger( "control_tests" ).debug(
"Started the gRPC test control service: port=%s", str(port_no)
)
server.start()
global _control_tests_port_no
_control_tests_port_no = port_no
started_event.set()
shutdown_event.wait()
server.stop( None )
server.wait_for_termination()
thread = threading.Thread( target=run_service, daemon=True )
thread.start()
# wait for the service to start (since the caller will probably try to connect
# to it as soon as we return a response).
started_event.wait( timeout=10 )
# wait for the gRPC server to end cleanly when we shutdown
def cleanup(): #pylint: disable=missing-docstring
thread.join()
globvars.cleanup_handlers.append( cleanup )
# return the service info to the caller
return jsonify( { "port": _control_tests_port_no } )
# ---------------------------------------------------------------------
@app.route( "/ping" )
def ping():
"""Let the caller know we're alive."""
return "pong: {}".format( INSTANCE_ID )
# ---------------------------------------------------------------------
@app.route( "/shutdown" )
def shutdown():
"""Shutdown the webapp (for testing porpoises)."""
request.environ.get( "werkzeug.server.shutdown" )()
return ""

@ -7,37 +7,70 @@ import urllib.request
import time
import glob
import click
# ---------------------------------------------------------------------
# monitor extra files for changes
extra_files = []
for fspec in ["config","static","templates"] :
fspec = os.path.abspath( os.path.join( os.path.dirname(__file__), fspec ) )
if os.path.isdir( fspec ):
files = [ os.path.join(fspec,f) for f in os.listdir(fspec) ]
files = [ f for f in files if os.path.isfile(f) and os.path.splitext(f)[1] not in [".swp"] ]
@click.command()
@click.option( "--addr","-a","bind_addr", help="Webapp server address (host:port)." )
@click.option( "--force-init-delay", default=0, help="Force the webapp to initialize (#seconds delay)." )
@click.option( "--debug","flask_debug", is_flag=True, default=False, help="Run Flask in debug mode." )
def main( bind_addr, force_init_delay, flask_debug ):
"""Run the vasl-templates webapp server."""
# initialize
from vasl_templates.webapp import app
port = None
if bind_addr:
words = bind_addr.split( ":" )
host = words[0]
if len(words) > 1:
port = words[1]
else:
files = glob.glob( fspec )
extra_files.extend( files )
# initialize
from vasl_templates.webapp import app
host = app.config.get( "FLASK_HOST", "localhost" )
port = app.config["FLASK_PORT_NO"]
debug = app.config.get( "FLASK_DEBUG", False )
def start_server():
"""Force the server to do "first request" initialization."""
# NOTE: This is not needed when running the desktop app (since it will request the home page),
# but if we're running just the server (i.e. from the console, or a Docker container), then
# sending a request, any request, will trigger the "first request" initialization (in particular,
# the download thread).
time.sleep( 5 )
url = "http://{}:{}/ping".format( host, port )
_ = urllib.request.urlopen( url )
threading.Thread( target=start_server, daemon=True ).start()
# run the server
app.run( host=host, port=port, debug=debug,
extra_files = extra_files
)
host = app.config.get( "FLASK_HOST", "localhost" )
if not port:
port = app.config.get( "FLASK_PORT_NO" )
if not flask_debug:
flask_debug = app.config.get( "FLASK_DEBUG", False )
# validate the configuration
if not host:
raise RuntimeError( "The server host was not set." )
if not port:
raise RuntimeError( "The server port was not set." )
# monitor extra files for changes
extra_files = []
fspecs = [ "static/", "templates/", "config/" ]
fspecs.extend( [ "tests/control_tests_servicer.py", "tests/proto/generated/" ] )
for fspec in fspecs:
fspec = os.path.abspath( os.path.join( os.path.dirname(__file__), fspec ) )
if os.path.isdir( fspec ):
files = [ os.path.join(fspec,f) for f in os.listdir(fspec) ]
files = [ f for f in files if os.path.isfile(f) and os.path.splitext(f)[1] not in [".swp"] ]
else:
files = glob.glob( fspec )
extra_files.extend( files )
# check if we should force webapp initialization
if force_init_delay > 0:
def _start_server():
"""Force the server to do "first request" initialization."""
# NOTE: This is not needed when running the desktop app (since it will request the home page),
# but if we're running just the server (i.e. from the console, or a Docker container), then
# it's useful to send a request (any request), since this will trigger "first request" initialization
# (in particular, starting the download thread).
time.sleep( force_init_delay )
url = "http://{}:{}/ping".format( host, port )
_ = urllib.request.urlopen( url )
threading.Thread( target=_start_server, daemon=True ).start()
# run the server
app.run( host=host, port=port, debug=flask_debug,
extra_files = extra_files
)
# ---------------------------------------------------------------------
if __name__ == "__main__":
main() #pylint: disable=no-value-for-parameter

@ -1,45 +0,0 @@
"""Webapp handlers for testing porpoises."""
import inspect
import base64
from flask import request, jsonify, abort
from vasl_templates.webapp import app
from vasl_templates.webapp.tests.remote import ControlTests
_control_tests = ControlTests( app )
# ---------------------------------------------------------------------
@app.route( "/control-tests/<action>" )
def control_tests( action ):
"""Accept commands from a remote test suite."""
# check if this functionality has been enabled
if not app.config.get( "ENABLE_REMOTE_TEST_CONTROL" ):
abort( 404 )
# figure out what we're being asked to do
func = getattr( _control_tests, action )
if not func:
abort( 404 )
# get any parameters
sig = inspect.signature( func )
kwargs = {}
for param in sig.parameters.values():
if param.name in ("vengine","vmod","gpids","key","val","dtype","fname","dname","extns_dtype","bin_data"):
kwargs[ param.name ] = request.args.get( param.name, param.default )
if param.name == "bin_data" and kwargs["bin_data"]:
kwargs["bin_data"] = base64.b64decode( kwargs["bin_data"] )
# execute the command
resp = func( **kwargs )
# return any response
if isinstance( resp, (str,list,dict) ):
return jsonify( resp )
else:
assert resp == _control_tests, "Methods should return self if there is no response data."
return "ok"

@ -0,0 +1,17 @@
cd `dirname "$0"`/proto
# initialize
rm -rf generated 2>/dev/null
mkdir generated
# compile the protobuf definitions
python -m grpc_tools.protoc \
--proto_path . \
--python_out=generated/ \
--grpc_python_out=generated/ \
control_tests.proto
# FUDGE! Fix a bogus import :-/
sed --in-place \
's/^import control_tests_pb2 as control__tests__pb2$/import vasl_templates.webapp.tests.proto.generated.control_tests_pb2 as control__tests__pb2/' \
generated/control_tests_pb2_grpc.py

@ -0,0 +1,227 @@
""" Allow the test suite to control a remote webapp server. """
import json
import base64
import grpc
from google.protobuf.empty_pb2 import Empty
from vasl_templates.webapp.tests.proto.generated.control_tests_pb2_grpc import ControlTestsStub
from vasl_templates.webapp.tests.proto.utils import enum_from_string
from vasl_templates.webapp.tests.proto.generated.control_tests_pb2 import \
SetVassalVersionRequest, SetVaslVersionRequest, SetVaslExtnInfoDirRequest, SetGpidRemappingsRequest, \
SetDataDirRequest, SetDefaultScenarioRequest, SetDefaultTemplatePackRequest, \
SetVehOrdNotesDirRequest, SetUserFilesDirRequest, \
SetAsaScenarioIndexRequest, SetRoarScenarioIndexRequest, \
DumpVsavRequest, GetVaslPiecesRequest, \
SetAppConfigValRequest, DeleteAppConfigValRequest, \
SaveTempFileRequest
# ---------------------------------------------------------------------
# NOTE: The API for this class should be kept in sync with ControlTestsServicer.
class ControlTests: #pylint: disable=too-many-public-methods
"""Control a remote webapp server."""
def __init__( self, addr ):
# initialize
channel = grpc.insecure_channel( addr )
self._stub = ControlTestsStub( channel )
self._caps = None
def has_capability( self, cap ) :
"""Check if the remote webapp has the specified capability."""
return cap in self._caps
def start_tests( self ):
"""Start a new test run."""
resp = self._stub.startTests( Empty() )
self._caps = set( resp.capabilities )
return self
def end_tests( self ):
"""End a test run."""
self._stub.endTests( Empty() )
self._caps = None
def get_vassal_versions( self ):
"""Get the available VASSAL versions."""
resp = self._stub.getVassalVersions( Empty() )
return resp.vassalVersions
def set_vassal_version( self, vassal_version ):
"""Set the VASSAL version."""
self._stub.setVassalVersion(
SetVassalVersionRequest( vassalVersion = vassal_version )
)
return self
def get_vasl_versions( self ):
"""Get the available VASL versions."""
resp = self._stub.getVaslVersions( Empty() )
return resp.vaslVersions
def set_vasl_version( self, vasl_mod, vasl_extns_type ):
"""Set the VASL version."""
vasl_extns_type = enum_from_string(
SetVaslVersionRequest.VaslExtnsType, #pylint: disable=no-member
vasl_extns_type or "{NONE}"
)
self._stub.setVaslVersion(
SetVaslVersionRequest( vaslVersion=vasl_mod, vaslExtnsType=vasl_extns_type )
)
return self
def get_vasl_extns( self ):
"""Get the VASL extensions."""
resp = self._stub.getVaslExtns( Empty() )
return json.loads( resp.vaslExtnsJson )
def set_vasl_extn_info_dir( self, dname ):
"""Set the VASL extensions info directory."""
self._stub.setVaslExtnInfoDir(
SetVaslExtnInfoDirRequest( dirName = dname )
)
return self
def set_gpid_remappings( self, gpid_remappings ):
"""Set the GPID remappings."""
self._stub.setGpidRemappings(
SetGpidRemappingsRequest( gpidRemappingsJson = json.dumps( gpid_remappings ) )
)
return self
def get_vasl_mod_warnings( self ):
"""Get the vasl_mod warnings."""
resp = self._stub.getVaslModWarnings( Empty() )
return resp.warnings
def set_data_dir( self, dtype ):
"""Set the data directory."""
dtype = enum_from_string( SetDataDirRequest.DirType, dtype ) #pylint: disable=no-member
self._stub.setDataDir(
SetDataDirRequest( dirType = dtype )
)
return self
def set_default_scenario( self, fname ):
"""Set the default scenario."""
self._stub.setDefaultScenario(
SetDefaultScenarioRequest( fileName = fname )
)
return self
def set_default_template_pack( self, template_pack ):
"""Set the default template pack."""
if isinstance( template_pack, str ) and template_pack.startswith(