From e6c37c49f983bb4bdda3232d9a3cd93cc8829627 Mon Sep 17 00:00:00 2001 From: Taka Date: Thu, 6 Dec 2018 11:46:50 +0000 Subject: [PATCH] Updated the tests so that they can be used with a remote server. --- .pylintrc | 1 + conftest.py | 65 ++++--- vasl_templates/webapp/__init__.py | 3 + vasl_templates/webapp/testing.py | 41 +++++ vasl_templates/webapp/tests/remote.py | 168 ++++++++++++++++++ .../webapp/tests/test_capabilities.py | 13 +- vasl_templates/webapp/tests/test_counters.py | 62 +++++-- .../webapp/tests/test_default_scenario.py | 12 +- vasl_templates/webapp/tests/test_snippets.py | 8 +- .../webapp/tests/test_template_packs.py | 11 +- .../webapp/tests/test_user_settings.py | 15 +- vasl_templates/webapp/tests/test_vassal.py | 89 ++++------ .../webapp/tests/test_vehicles_ordnance.py | 53 +++--- vasl_templates/webapp/tests/utils.py | 39 ++-- vasl_templates/webapp/vassal.py | 2 +- 15 files changed, 401 insertions(+), 181 deletions(-) create mode 100644 vasl_templates/webapp/testing.py create mode 100644 vasl_templates/webapp/tests/remote.py diff --git a/.pylintrc b/.pylintrc index c863eaf..1ce6b75 100644 --- a/.pylintrc +++ b/.pylintrc @@ -141,6 +141,7 @@ disable=print-statement, global-statement, too-few-public-methods, duplicate-code, # can't get it to shut up about @pytest.mark.skipif's :-/ + no-else-return # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/conftest.py b/conftest.py index 697889b..a593fbd 100644 --- a/conftest.py +++ b/conftest.py @@ -14,7 +14,7 @@ from vasl_templates.webapp import app app.testing = True from vasl_templates.webapp.tests import utils -FLASK_WEBAPP_PORT = 5001 +FLASK_WEBAPP_PORT = 5011 # --------------------------------------------------------------------- @@ -24,6 +24,10 @@ def pytest_addoption( parser ): # NOTE: This file needs to be in the project root for this to work :-/ # add test options + parser.addoption( + "--server-url", action="store", dest="server_url", default=None, + help="Webapp server to test against." + ) # NOTE: Chrome seems to be ~15% faster than Firefox, headless ~5% faster than headful. parser.addoption( "--webdriver", action="store", dest="webdriver", default="chrome", @@ -73,6 +77,10 @@ def pytest_addoption( parser ): def webapp(): """Launch the webapp.""" + # initialize + server_url = pytest.config.option.server_url #pylint: disable=no-member + logging.disable( logging.CRITICAL ) + # initialize # WTF?! https://github.com/pallets/flask/issues/824 def make_webapp_url( endpoint, **kwargs ): @@ -94,39 +102,40 @@ def webapp(): # to avoid problems if something else uses the clipboard while the tests are running. kwargs["store_clipboard"] = 1 url = url_for( endpoint, _external=True, **kwargs ) - return url.replace( "localhost/", "localhost:{}/".format(FLASK_WEBAPP_PORT) ) + if server_url: + url = url.replace( "http://localhost", server_url ) + else: + url = url.replace( "localhost/", "localhost:{}/".format(FLASK_WEBAPP_PORT) ) + return url app.url_for = make_webapp_url - # configure the webapp to use our test data - # NOTE: Can't seem to change constants.DATA_DIR (probably some pytest funkiness :-/) - app.config["DATA_DIR"] = os.path.join( os.path.split(__file__)[0], "vasl_templates/webapp/tests/fixtures/data" ) - - # start the webapp server (in a background thread) - logging.disable( logging.CRITICAL ) - thread = threading.Thread( - target = lambda: app.run( host="0.0.0.0", port=FLASK_WEBAPP_PORT, use_reloader=False ) - ) - thread.start() - - # wait for the server to start up - def is_ready(): - """Try to connect to the webapp server.""" - try: - resp = urllib.request.urlopen( app.url_for("ping") ).read() - assert resp == b"pong" - return True - except URLError: - return False - except Exception as ex: #pylint: disable=broad-except - assert False, "Unexpected exception: {}".format(ex) - utils.wait_for( 5, is_ready ) + # check if we need to start a local webapp server + if not server_url: + # yup - make it so + thread = threading.Thread( + target = lambda: app.run( host="0.0.0.0", port=FLASK_WEBAPP_PORT, use_reloader=False ) + ) + thread.start() + # wait for the server to start up + def is_ready(): + """Try to connect to the webapp server.""" + try: + resp = urllib.request.urlopen( app.url_for("ping") ).read() + assert resp == b"pong" + return True + except URLError: + return False + except Exception as ex: #pylint: disable=broad-except + assert False, "Unexpected exception: {}".format(ex) + utils.wait_for( 5, is_ready ) # return the server to the caller yield app - # shutdown the webapp server - urllib.request.urlopen( app.url_for("shutdown") ).read() - thread.join() + # shutdown the local webapp server + if not server_url: + urllib.request.urlopen( app.url_for("shutdown") ).read() + thread.join() # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vasl_templates/webapp/__init__.py b/vasl_templates/webapp/__init__.py index 4abf579..9d1715c 100644 --- a/vasl_templates/webapp/__init__.py +++ b/vasl_templates/webapp/__init__.py @@ -60,6 +60,9 @@ import vasl_templates.webapp.vo #pylint: disable=cyclic-import import vasl_templates.webapp.snippets #pylint: disable=cyclic-import import vasl_templates.webapp.files #pylint: disable=cyclic-import import vasl_templates.webapp.vassal #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 # --------------------------------------------------------------------- diff --git a/vasl_templates/webapp/testing.py b/vasl_templates/webapp/testing.py new file mode 100644 index 0000000..bca6d87 --- /dev/null +++ b/vasl_templates/webapp/testing.py @@ -0,0 +1,41 @@ +"""Webapp handlers for testing porpoises.""" + +import inspect + +from flask import request, jsonify, abort + +from vasl_templates.webapp import app +from vasl_templates.webapp.tests.remote import ControlTests + +# --------------------------------------------------------------------- + +@app.route( "/control-tests/" ) +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 + controller = ControlTests( app ) + func = getattr( controller, 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","ddtype","fname","dname"): + kwargs[ param.name ] = request.args.get( param.name, param.default ) + + # execute the command + resp = func( **kwargs ) + + # return any response + if isinstance( resp, (str,list,dict) ): + return jsonify( resp ) + else: + assert resp == controller, "Methods should return self if there is no response data." + return "ok" diff --git a/vasl_templates/webapp/tests/remote.py b/vasl_templates/webapp/tests/remote.py new file mode 100644 index 0000000..9146ae8 --- /dev/null +++ b/vasl_templates/webapp/tests/remote.py @@ -0,0 +1,168 @@ +"""Allow a remote server to be controlled during tests. + +We sometimes make changes to the webapp server during tests, and while we used to do that using pytest's monkeypatch, +that will not work if we are talking to a remote (i.e. in another process) server. This module defines the things +that can be changed during the course of tests, and a simple RPC mechanism that lets them be executed remotely. +It needs to be enabled in the server via the ENABLE_REMOTE_TEST_CONTROL debug switch. +""" + +import os +import urllib.request +import json +import glob +import logging +import random + +import pytest + +from vasl_templates.webapp import app +from vasl_templates.webapp.config.constants import DATA_DIR +from vasl_templates.webapp import main as webapp_main +from vasl_templates.webapp import snippets as webapp_snippets +from vasl_templates.webapp import files as webapp_files +from vasl_templates.webapp.file_server import utils as webapp_file_server_utils +from vasl_templates.webapp.file_server.vasl_mod import VaslMod + +_logger = logging.getLogger( "control_tests" ) + +# --------------------------------------------------------------------- + +class ControlTests: + """Control a remote server during tests.""" + + def __init__( self, webapp ): + self.webapp = webapp + try: + self.server_url = pytest.config.option.server_url #pylint: disable=no-member + except AttributeError: + self.server_url = None + + def __getattr__( self, name ): + """Generic entry point for handling control requests.""" + if name.startswith( ("get_","set_") ): + # check if we are talking to a local or remote server + if self.server_url: + # remote: return a function that will invoke the handler function on the remote server + def call_remote( **kwargs ): #pylint: disable=missing-docstring + return self._remote_test_control( name, **kwargs ) + return call_remote + else: + # local: return the actual handler function + return getattr( self, "_"+name ) + raise AttributeError( name ) + + def _remote_test_control( self, action, **kwargs ): + """Invoke a handler function on the remote server.""" + resp = urllib.request.urlopen( + self.webapp.url_for( "control_tests", action=action, **kwargs ) + ).read() + if resp == b"ok": + return self + else: + return json.loads( resp.decode( "utf-8" ) ) + + def _set_data_dir( self, ddtype=None ): + """Set the webapp's data directory.""" + if ddtype == "real": + dname = DATA_DIR + elif ddtype == "test": + dname = os.path.join( os.path.split(__file__)[0], "fixtures/data" ) + else: + raise RuntimeError( "Unknown data dir type: {}".format( ddtype ) ) + _logger.info( "Setting data dir: %s", dname ) + self.webapp.config[ "DATA_DIR" ] = dname + return self + + def _set_default_scenario( self, fname=None ): + """Set the default scenario.""" + if fname: + dname = os.path.join( os.path.split(__file__)[0], "fixtures" ) + fname = os.path.join( dname, fname ) + _logger.info( "Setting default scenario: %s", fname ) + webapp_main.default_scenario = fname + return self + + def _set_default_template_pack( self, dname=None ): + """Set the default template pack.""" + if dname: + dname2 = os.path.join( os.path.split(__file__)[0], "fixtures" ) + dname = os.path.join( dname2, dname ) + _logger.info( "Setting default template pack: %s", dname ) + webapp_snippets.default_template_pack = dname + return self + + def _set_gpid_remappings( self, gpids=None ): #pylint: disable=no-self-use + """Configure the GPID remappings.""" + if isinstance( gpids, str ): + gpids = json.loads( gpids.replace( "'", '"' ) ) + gpids = { int(k): v for k,v in gpids.items() } + _logger.info( "Setting GPID remappings: %s", gpids ) + prev_gpid_mappings = webapp_file_server_utils.GPID_REMAPPINGS + webapp_file_server_utils.GPID_REMAPPINGS = gpids + return prev_gpid_mappings + + def _get_vasl_mods( self ): + """Return the available VASL modules.""" + fnames = self._do_get_vasl_mods() + _logger.debug( "Returning VASL modules:\n%s", + "\n".join( "- {}".format( f ) for f in fnames ) + ) + return fnames + + def _do_get_vasl_mods( self ): #pylint: disable=no-self-use + """Return the available VASL modules.""" + try: + dname = pytest.config.option.vasl_mods #pylint: disable=no-member + except AttributeError: + dname = app.config["TEST_VASL_MODS"] + fspec = os.path.join( dname, "*.vmod" ) + return glob.glob( fspec ) + + def _set_vasl_mod( self, vmod=None ): + """Install a VASL module.""" + if vmod is None: + _logger.info( "Installing VASL module: %s", vmod ) + webapp_files.vasl_mod = None + else: + fnames = self._do_get_vasl_mods() + if vmod == "random": + # NOTE: Some tests require a VASL module to be loaded, and since they should all + # should behave in the same way, it doesn't matter which one we load. + fname = random.choice( fnames ) + else: + assert vmod in fnames + fname = vmod + _logger.info( "Installing VASL module: %s", fname ) + webapp_files.vasl_mod = VaslMod( fname, DATA_DIR ) + return self + + def _get_vassal_engines( self ): + """Get the available VASSAL engines.""" + vassal_engines = self._do_get_vassal_engines() + _logger.debug( "Returning VASSAL engines:\n%s", + "\n".join( "- {}".format( ve ) for ve in vassal_engines ) + ) + return vassal_engines + + def _do_get_vassal_engines( self ): #pylint: disable=no-self-use + """Get the available VASSAL engines.""" + try: + dname = pytest.config.option.vassal #pylint: disable=no-member + except AttributeError: + dname = app.config[ "TEST_VASSAL_ENGINES"] + vassal_engines = [] + for root,_,fnames in os.walk( dname ): + for fname in fnames: + if fname == "Vengine.jar": + if root.endswith( "/lib" ): + root = root[:-4] + vassal_engines.append( root ) + return vassal_engines + + def _set_vassal_engine( self, vengine=None ): + """Install a VASSAL engine.""" + if vengine: + assert vengine in self._do_get_vassal_engines() + _logger.info( "Installing VASSAL engine: %s", vengine ) + app.config["VASSAL_DIR"] = vengine + return self diff --git a/vasl_templates/webapp/tests/test_capabilities.py b/vasl_templates/webapp/tests/test_capabilities.py index 8cd42e5..02c22c3 100644 --- a/vasl_templates/webapp/tests/test_capabilities.py +++ b/vasl_templates/webapp/tests/test_capabilities.py @@ -8,12 +8,11 @@ from selenium.webdriver.common.keys import Keys from vasl_templates.webapp.tests.utils import \ init_webapp, select_menu_option, select_tab, click_dialog_button, \ - load_vasl_mod, find_child, find_children, wait_for_clipboard, \ + find_child, find_children, wait_for_clipboard, \ set_scenario_date from vasl_templates.webapp.tests.test_vo_reports import get_vo_report from vasl_templates.webapp.tests.test_vehicles_ordnance import add_vo from vasl_templates.webapp.tests.test_scenario_persistence import save_scenario, load_scenario -from vasl_templates.webapp.config.constants import DATA_DIR as REAL_DATA_DIR # --------------------------------------------------------------------- @@ -555,13 +554,15 @@ def test_custom_capabilities( webapp, webdriver ): #pylint: disable=too-many-sta not pytest.config.option.vasl_mods, #pylint: disable=no-member reason = "--vasl-mods not specified" ) #pylint: disable=too-many-statements -def test_capability_updates_in_ui( webapp, webdriver, monkeypatch ): +def test_capability_updates_in_ui( webapp, webdriver ): """Ensure that capabilities are updated in the UI correctly.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - load_vasl_mod( REAL_DATA_DIR, monkeypatch ) - init_webapp( webapp, webdriver, scenario_persistence=1 ) + init_webapp( webapp, webdriver, scenario_persistence=1, + reset = lambda ct: + ct.set_data_dir( ddtype="real" ) \ + .set_vasl_mod( vmod="random" ) + ) # load the scenario scenario_data = { diff --git a/vasl_templates/webapp/tests/test_counters.py b/vasl_templates/webapp/tests/test_counters.py index 6145dec..2ce3d72 100644 --- a/vasl_templates/webapp/tests/test_counters.py +++ b/vasl_templates/webapp/tests/test_counters.py @@ -2,7 +2,6 @@ import os import io -import glob import json import re import urllib.request @@ -10,11 +9,12 @@ import urllib.request import pytest import tabulate +from vasl_templates.webapp.file_server.vasl_mod import VaslMod from vasl_templates.webapp.file_server.utils import get_vo_gpids -from vasl_templates.webapp.file_server import utils as webapp_file_server_utils -from vasl_templates.webapp.config.constants import DATA_DIR as REAL_DATA_DIR -from vasl_templates.webapp.tests.utils import init_webapp, load_vasl_mod, select_tab, find_child, find_children +from vasl_templates.webapp.config.constants import DATA_DIR +from vasl_templates.webapp.tests.utils import init_webapp, select_tab, find_child, find_children from vasl_templates.webapp.tests.test_scenario_persistence import load_scenario +from vasl_templates.webapp.tests.remote import ControlTests # --------------------------------------------------------------------- @@ -26,13 +26,13 @@ from vasl_templates.webapp.tests.test_scenario_persistence import load_scenario pytest.config.option.short_tests, #pylint: disable=no-member reason = "--short-tests specified" ) #pylint: disable=too-many-statements -def test_counter_images( webapp, monkeypatch ): +def test_counter_images( webapp ): """Test that counter images are served correctly.""" # NOTE: This is ridiculously slow on Windows :-/ # figure out which pieces we're interested in - gpids = get_vo_gpids( REAL_DATA_DIR ) + gpids = get_vo_gpids( DATA_DIR ) def check_images( check_front, check_back ): #pylint: disable=unused-argument """Check getting the front and back images for each counter.""" @@ -49,7 +49,8 @@ def test_counter_images( webapp, monkeypatch ): assert locals()["check_"+side]( resp_code, resp_data ) # test counter images when no VASL module has been configured - load_vasl_mod( None, monkeypatch ) + control_tests = ControlTests( webapp ) + control_tests.set_vasl_mod( vmod=None ) fname = os.path.join( os.path.split(__file__)[0], "../static/images/missing-image.png" ) missing_image_data = open( fname, "rb" ).read() check_images( @@ -60,13 +61,20 @@ def test_counter_images( webapp, monkeypatch ): # test each VASL module file in the specified directory fname = os.path.join( os.path.split(__file__)[0], "fixtures/vasl-pieces.txt" ) expected_vasl_pieces = open( fname, "r" ).read() - fspec = os.path.join( pytest.config.option.vasl_mods, "*.vmod" ) #pylint: disable=no-member - for fname in glob.glob( fspec ): + vasl_mods = control_tests.get_vasl_mods() + for vasl_mod in vasl_mods: # install the VASL module file - vasl_mod = load_vasl_mod( REAL_DATA_DIR, monkeypatch, fname=os.path.split(fname)[1] ) + control_tests.set_vasl_mod( vmod=vasl_mod ) + + # NOTE: We assume we have access to the same VASL modules as the server, but the path on the webserver + # might be different to what it is locally, so we translate it here. + fname = os.path.split( vasl_mod )[1] + vasl_mods_dir = pytest.config.option.vasl_mods #pylint: disable=no-member + fname = os.path.join( vasl_mods_dir, fname ) # check the pieces loaded + vasl_mod = VaslMod( fname, DATA_DIR ) buf = io.StringIO() _dump_pieces( vasl_mod, buf ) assert buf.getvalue() == expected_vasl_pieces @@ -96,11 +104,11 @@ def _dump_pieces( vasl_mod, out ): not pytest.config.option.vasl_mods, #pylint: disable=no-member reason = "--vasl-mods not specified" ) -def test_gpid_remapping( webapp, webdriver, monkeypatch ): +def test_gpid_remapping( webapp, webdriver ): """Test GPID remapping.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) + control_tests = init_webapp( webapp, webdriver ) def check_gpid_image( gpid ): """Check if we can get the image for the specified GPID.""" @@ -127,8 +135,11 @@ def test_gpid_remapping( webapp, webdriver, monkeypatch ): def do_test( vasl_mod, valid_images ): """Do the test.""" # initialize (using the specified VASL vmod) - load_vasl_mod( REAL_DATA_DIR, monkeypatch, vasl_mod ) - init_webapp( webapp, webdriver, scenario_persistence=1 ) + init_webapp( webapp, webdriver, scenario_persistence=1, + reset = lambda ct: + ct.set_data_dir( ddtype="real" ) \ + .set_vasl_mod( vmod=vasl_mod ) + ) load_scenario( scenario_data ) # check that the German vehicles loaded correctly select_tab( "ob1" ) @@ -142,11 +153,24 @@ def test_gpid_remapping( webapp, webdriver, monkeypatch ): fname = os.path.join( os.path.split(__file__)[0], "fixtures/gpid-remapping.json" ) scenario_data = json.load( open( fname, "r" ) ) + # locate the VASL modules + vasl_mods = control_tests.get_vasl_mods() + def find_vasl_mod( version ): + """Find the VASL module for the specified version.""" + matches = [ vmod for vmod in vasl_mods if "vasl-{}.vmod".format(version) in vmod ] + assert len(matches) == 1 + return matches[0] + # run the tests using VASL 6.4.2 and 6.4.3 - do_test( "vasl-6.4.2.vmod", True ) - do_test( "vasl-6.4.3.vmod", True ) + do_test( find_vasl_mod("6.4.2"), True ) + do_test( find_vasl_mod("6.4.3"), True ) # disable GPID remapping and try again - monkeypatch.setattr( webapp_file_server_utils, "GPID_REMAPPINGS", {} ) - do_test( "vasl-6.4.2.vmod", True ) - do_test( "vasl-6.4.3.vmod", False ) + prev_gpid_mappings = control_tests.set_gpid_remappings( gpids={} ) + try: + do_test( find_vasl_mod("6.4.2"), True ) + do_test( find_vasl_mod("6.4.3"), False ) + finally: + # NOTE: This won't get done if Python exits unexpectedly in the try block, + # which will leave the server in the wrong state if it's remote. + control_tests.set_gpid_remappings( gpids=prev_gpid_mappings ) diff --git a/vasl_templates/webapp/tests/test_default_scenario.py b/vasl_templates/webapp/tests/test_default_scenario.py index 5a79f4a..c3b86ef 100644 --- a/vasl_templates/webapp/tests/test_default_scenario.py +++ b/vasl_templates/webapp/tests/test_default_scenario.py @@ -1,24 +1,20 @@ """Test the loading of the default scenario.""" -import os from selenium.webdriver.support.ui import Select from selenium.webdriver.common.keys import Keys -from vasl_templates.webapp import main from vasl_templates.webapp.tests.utils import select_tab, find_child, get_sortable_entry_text, \ wait_for, init_webapp # --------------------------------------------------------------------- -def test_default_scenario( webapp, webdriver, monkeypatch ): +def test_default_scenario( webapp, webdriver ): """Test loading the default scenario.""" - # configure a new default scenario - fname = os.path.join( os.path.split(__file__)[0], "fixtures/new-default-scenario.json" ) - monkeypatch.setattr( main, "default_scenario", fname ) - # initialize - init_webapp( webapp, webdriver ) + init_webapp( webapp, webdriver, + reset = lambda ct: ct.set_default_scenario( fname="new-default-scenario.json" ) + ) # wait for the scenario to load elem = find_child( "input[name='SCENARIO_NAME']" ) diff --git a/vasl_templates/webapp/tests/test_snippets.py b/vasl_templates/webapp/tests/test_snippets.py index bf28680..aaf59be 100644 --- a/vasl_templates/webapp/tests/test_snippets.py +++ b/vasl_templates/webapp/tests/test_snippets.py @@ -2,7 +2,6 @@ from selenium.webdriver.common.keys import Keys -from vasl_templates.webapp.config.constants import DATA_DIR as REAL_DATA_DIR from vasl_templates.webapp.tests.utils import \ init_webapp, select_tab, select_tab_for_elem, set_template_params, wait_for_clipboard, \ get_stored_msg, set_stored_msg_marker, find_child, find_children, adjust_html, \ @@ -13,12 +12,13 @@ from vasl_templates.webapp.tests.test_scenario_persistence import load_scenario, # --------------------------------------------------------------------- -def test_snippet_ids( webapp, webdriver, monkeypatch ): +def test_snippet_ids( webapp, webdriver ): """Check that snippet ID's are generated correctly.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - init_webapp( webapp, webdriver, scenario_persistence=1 ) + init_webapp( webapp, webdriver, scenario_persistence=1, + reset = lambda ct: ct.set_data_dir( ddtype="real" ) + ) # load a scenario (so that we get some sortable's) scenario_data = { diff --git a/vasl_templates/webapp/tests/test_template_packs.py b/vasl_templates/webapp/tests/test_template_packs.py index f69e05e..852eba3 100644 --- a/vasl_templates/webapp/tests/test_template_packs.py +++ b/vasl_templates/webapp/tests/test_template_packs.py @@ -7,7 +7,6 @@ import base64 from selenium.webdriver.support.ui import Select -from vasl_templates.webapp import snippets from vasl_templates.webapp.tests.utils import \ select_tab, select_menu_option, wait_for_clipboard, get_stored_msg, set_stored_msg, set_stored_msg_marker,\ add_simple_note, for_each_template, find_child, find_children, wait_for, \ @@ -72,15 +71,13 @@ def test_zip_files( webapp, webdriver ): # --------------------------------------------------------------------- -def test_new_default_template_pack( webapp, webdriver, monkeypatch ): +def test_new_default_template_pack( webapp, webdriver ): """Test changing the default template pack.""" - # configure a new default template pack - dname = os.path.join( os.path.split(__file__)[0], "fixtures/template-packs/new-default/" ) - monkeypatch.setattr( snippets, "default_template_pack", dname ) - # initialize - init_webapp( webapp, webdriver ) + init_webapp( webapp, webdriver, + reset = lambda ct: ct.set_default_template_pack( dname="template-packs/new-default/" ) + ) # check that the new templates are being used _check_snippets( lambda tid: "New default {}.".format( tid.upper() ) ) diff --git a/vasl_templates/webapp/tests/test_user_settings.py b/vasl_templates/webapp/tests/test_user_settings.py index 1484c68..3f3548f 100644 --- a/vasl_templates/webapp/tests/test_user_settings.py +++ b/vasl_templates/webapp/tests/test_user_settings.py @@ -11,16 +11,16 @@ from vasl_templates.webapp.tests.utils import \ from vasl_templates.webapp.tests.test_vehicles_ordnance import add_vo from vasl_templates.webapp.tests.test_scenario_persistence import save_scenario, load_scenario from vasl_templates.webapp.tests.test_template_packs import upload_template_pack_file -from vasl_templates.webapp.config.constants import DATA_DIR as REAL_DATA_DIR # --------------------------------------------------------------------- -def test_include_vasl_images_in_snippets( webapp, webdriver, monkeypatch ): +def test_include_vasl_images_in_snippets( webapp, webdriver ): """Test including VASL counter images in snippets.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - init_webapp( webapp, webdriver ) + init_webapp( webapp, webdriver, + reset = lambda ct: ct.set_data_dir( ddtype="real" ) + ) # add a vehicle add_vo( webdriver, "vehicles", 1, "PzKpfw IB (Tt)" ) @@ -52,12 +52,13 @@ def test_include_vasl_images_in_snippets( webapp, webdriver, monkeypatch ): # --------------------------------------------------------------------- -def test_include_flags_in_snippets( webapp, webdriver, monkeypatch ): +def test_include_flags_in_snippets( webapp, webdriver ): """Test including flags in snippets.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - init_webapp( webapp, webdriver ) + init_webapp( webapp, webdriver, + reset = lambda ct: ct.set_data_dir( ddtype="real" ) + ) # prepare the scenario select_tab( "ob1" ) diff --git a/vasl_templates/webapp/tests/test_vassal.py b/vasl_templates/webapp/tests/test_vassal.py index 5c935f2..e77853c 100644 --- a/vasl_templates/webapp/tests/test_vassal.py +++ b/vasl_templates/webapp/tests/test_vassal.py @@ -1,7 +1,6 @@ """ Test VASSAL integration. """ import os -import glob import re import json import base64 @@ -10,7 +9,6 @@ import typing.re #pylint: disable=import-error import pytest -from vasl_templates.webapp.config.constants import DATA_DIR as REAL_DATA_DIR from vasl_templates.webapp.vassal import VassalShim from vasl_templates.webapp.utils import TempFile, change_extn from vasl_templates.webapp.tests.utils import \ @@ -23,15 +21,13 @@ from vasl_templates.webapp.tests.test_scenario_persistence import load_scenario, @pytest.mark.skipif( not pytest.config.option.vasl_mods, reason="--vasl-mods not specified" ) #pylint: disable=no-member @pytest.mark.skipif( not pytest.config.option.vassal, reason="--vassal not specified" ) #pylint: disable=no-member @pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member -def test_full_update( webapp, webdriver, monkeypatch ): +def test_full_update( webapp, webdriver ): """Test updating a scenario that contains the full set of snippets.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - init_webapp( webapp, webdriver, vsav_persistence=1 ) - - # NOTE: We disable this for speed, since we don't care about label positioning. - monkeypatch.setitem( webapp.config, "DISABLE_UPDATE_VSAV_SCREENSHOTS", True ) + control_tests = init_webapp( webapp, webdriver, vsav_persistence=1, + reset = lambda ct: ct.set_data_dir( ddtype="real" ) + ) # load the scenario fields SCENARIO_PARAMS = { @@ -144,22 +140,20 @@ def test_full_update( webapp, webdriver, monkeypatch ): assert updated_vsav_data == b"No changes." # run the test against all versions of VASSAL+VASL - _run_tests( webapp, monkeypatch, do_test, True ) + _run_tests( control_tests, do_test, True ) # --------------------------------------------------------------------- @pytest.mark.skipif( not pytest.config.option.vasl_mods, reason="--vasl-mods not specified" ) #pylint: disable=no-member @pytest.mark.skipif( not pytest.config.option.vassal, reason="--vassal not specified" ) #pylint: disable=no-member @pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member -def test_latw_autocreate( webapp, webdriver, monkeypatch ): +def test_latw_autocreate( webapp, webdriver ): """Test auto-creation of LATW labels.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - init_webapp( webapp, webdriver, vsav_persistence=1 ) - - # NOTE: We disable this for speed, since we don't care about label positioning. - monkeypatch.setitem( webapp.config, "DISABLE_UPDATE_VSAV_SCREENSHOTS", True ) + control_tests = init_webapp( webapp, webdriver, vsav_persistence=1, + reset = lambda ct: ct.set_data_dir( ddtype="real" ) + ) # NOTE: We're only interested in what happens with the LATW labels, we ignore everything else. ignore_labels = [ "scenario", "players", "victory_conditions" ] @@ -217,22 +211,20 @@ def test_latw_autocreate( webapp, webdriver, monkeypatch ): # NOTE: We're testing the logic in the front/back-ends that determine whether LATW labels # get created/updated/deleted, not the interaction with VASSAL, so we don't need to test # against every VASSAL+VASL combination (although we can, if we want, but it'll be slow!) - _run_tests( webapp, monkeypatch, do_test, False ) + _run_tests( control_tests, do_test, False ) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @pytest.mark.skipif( not pytest.config.option.vasl_mods, reason="--vasl-mods not specified" ) #pylint: disable=no-member @pytest.mark.skipif( not pytest.config.option.vassal, reason="--vassal not specified" ) #pylint: disable=no-member @pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member -def test_latw_update( webapp, webdriver, monkeypatch ): +def test_latw_update( webapp, webdriver ): """Test updating of LATW labels.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - init_webapp( webapp, webdriver, vsav_persistence=1 ) - - # NOTE: We disable this for speed, since we don't care about label positioning. - monkeypatch.setitem( webapp.config, "DISABLE_UPDATE_VSAV_SCREENSHOTS", True ) + control_tests = init_webapp( webapp, webdriver, vsav_persistence=1, + reset = lambda ct: ct.set_data_dir( ddtype="real" ) + ) # NOTE: We're only interested in what happens with the LATW labels, we ignore everything else. ignore_labels = [ "scenario", "players", "victory_conditions" ] @@ -274,18 +266,18 @@ def test_latw_update( webapp, webdriver, monkeypatch ): # NOTE: We're testing the logic in the front/back-ends that determine whether LATW labels # get created/updated/deleted, not the interaction with VASSAL, so we don't need to test # against every VASSAL+VASL combination (although we can, if we want, but it'll be slow!) - _run_tests( webapp, monkeypatch, do_test, False ) + _run_tests( control_tests, do_test, False ) # --------------------------------------------------------------------- @pytest.mark.skipif( not pytest.config.option.vasl_mods, reason="--vasl-mods not specified" ) #pylint: disable=no-member @pytest.mark.skipif( not pytest.config.option.vassal, reason="--vassal not specified" ) #pylint: disable=no-member @pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member -def test_dump_vsav( webapp, webdriver, monkeypatch ): +def test_dump_vsav( webapp, webdriver ): """Test dumping a scenario.""" # initialize - init_webapp( webapp, webdriver ) + control_tests = init_webapp( webapp, webdriver ) def do_test(): #pylint: disable=missing-docstring @@ -300,22 +292,20 @@ def test_dump_vsav( webapp, webdriver, monkeypatch ): assert vsav_dump == expected # run the test against all versions of VASSAL+VASL - _run_tests( webapp, monkeypatch, do_test, True ) + _run_tests( control_tests, do_test, True ) # --------------------------------------------------------------------- @pytest.mark.skipif( not pytest.config.option.vasl_mods, reason="--vasl-mods not specified" ) #pylint: disable=no-member @pytest.mark.skipif( not pytest.config.option.vassal, reason="--vassal not specified" ) #pylint: disable=no-member @pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member -def test_legacy_labels( webapp, webdriver, monkeypatch ): +def test_legacy_labels( webapp, webdriver ): """Test detection and updating of legacy labels.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - init_webapp( webapp, webdriver, vsav_persistence=1, scenario_persistence=1 ) - - # NOTE: We disable this for speed, since we don't care about label positioning. - monkeypatch.setitem( webapp.config, "DISABLE_UPDATE_VSAV_SCREENSHOTS", True ) + control_tests = init_webapp( webapp, webdriver, vsav_persistence=1, scenario_persistence=1, + reset = lambda ct: ct.set_data_dir( ddtype="real" ) + ) def do_test(): #pylint: disable=missing-docstring @@ -361,22 +351,20 @@ def test_legacy_labels( webapp, webdriver, monkeypatch ): } ) # run the test - _run_tests( webapp, monkeypatch, do_test, False ) + _run_tests( control_tests, do_test, False ) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @pytest.mark.skipif( not pytest.config.option.vasl_mods, reason="--vasl-mods not specified" ) #pylint: disable=no-member @pytest.mark.skipif( not pytest.config.option.vassal, reason="--vassal not specified" ) #pylint: disable=no-member @pytest.mark.skipif( pytest.config.option.short_tests, reason="--short-tests specified" ) #pylint: disable=no-member -def test_legacy_latw_labels( webapp, webdriver, monkeypatch ): +def test_legacy_latw_labels( webapp, webdriver ): """Test detection and updating of legacy LATW labels.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - init_webapp( webapp, webdriver, vsav_persistence=1, scenario_persistence=1 ) - - # NOTE: We disable this for speed, since we don't care about label positioning. - monkeypatch.setitem( webapp.config, "DISABLE_UPDATE_VSAV_SCREENSHOTS", True ) + control_tests = init_webapp( webapp, webdriver, vsav_persistence=1, scenario_persistence=1, + reset = lambda ct: ct.set_data_dir( ddtype="real" ) + ) def do_test(): #pylint: disable=missing-docstring @@ -441,29 +429,20 @@ def test_legacy_latw_labels( webapp, webdriver, monkeypatch ): assert len( [ lbl for lbl in labels if "vasl-templates:id" not in lbl ] ) == 6 # run the test - _run_tests( webapp, monkeypatch, do_test, False ) + _run_tests( control_tests, do_test, False ) # --------------------------------------------------------------------- -def _run_tests( webapp, monkeypatch, func, test_all ): +def _run_tests( control_tests, func, test_all ): """Run the test function for each combination of VASSAL + VASL. This is, of course, going to be insanely slow, since we need to spin up a JVM and initialize VASSAL/VASL each time :-/ """ - # locate all VASL modules - vasl_mods_dir = pytest.config.option.vasl_mods #pylint: disable=no-member - fspec = os.path.join( vasl_mods_dir, "*.vmod" ) - vasl_mods = glob.glob( fspec ) - - # locate all VASSAL engines - vassal_engines = [] - vassal_dir = pytest.config.option.vassal #pylint: disable=no-member - for root,_,fnames in os.walk( vassal_dir ): - for fname in fnames: - if fname == "Vengine.jar": - vassal_engines.append( root ) + # locate all VASL modules and VASSAL engines + vasl_mods = control_tests.get_vasl_mods() + vassal_engines = control_tests.get_vassal_engines() # check if we want to test all VASSAL+VASL combinations (nb: if not, we test against only one combination, # and since they all should give the same results, it doesn't matter which one. @@ -473,9 +452,9 @@ def _run_tests( webapp, monkeypatch, func, test_all ): # run the test for each VASSAL+VASL for vassal_engine in vassal_engines: - monkeypatch.setitem( webapp.config, "VASSAL_DIR", vassal_engine ) + control_tests.set_vassal_engine( vengine=vassal_engine ) for vasl_mod in vasl_mods: - monkeypatch.setitem( webapp.config, "VASL_MOD", vasl_mod ) + control_tests.set_vasl_mod( vmod=vasl_mod ) func() # --------------------------------------------------------------------- diff --git a/vasl_templates/webapp/tests/test_vehicles_ordnance.py b/vasl_templates/webapp/tests/test_vehicles_ordnance.py index f898f92..ffd22c8 100644 --- a/vasl_templates/webapp/tests/test_vehicles_ordnance.py +++ b/vasl_templates/webapp/tests/test_vehicles_ordnance.py @@ -12,10 +12,10 @@ from selenium.common.exceptions import WebDriverException from vasl_templates.webapp.tests.test_scenario_persistence import load_scenario, save_scenario from vasl_templates.webapp.tests.utils import \ - init_webapp, load_vasl_mod, get_nationalities, select_tab, set_template_params, find_child, find_children, \ + init_webapp, get_nationalities, select_tab, set_template_params, find_child, find_children, \ wait_for_clipboard, click_dialog_button, select_menu_option, select_droplist_val, \ set_stored_msg_marker, get_stored_msg, get_sortable_vo_names -from vasl_templates.webapp.config.constants import DATA_DIR as REAL_DATA_DIR +from vasl_templates.webapp.config.constants import DATA_DIR # --------------------------------------------------------------------- @@ -229,13 +229,15 @@ def test_variable_capabilities( webapp, webdriver ): not pytest.config.option.vasl_mods, #pylint: disable=no-member reason = "--vasl-mods not specified" ) -def test_html_names( webapp, webdriver, monkeypatch ): +def test_html_names( webapp, webdriver ): """Test handling of vehicles/ordnance that have HTML in their name.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - load_vasl_mod( REAL_DATA_DIR, monkeypatch ) - init_webapp( webapp, webdriver ) + init_webapp( webapp, webdriver, + reset = lambda ct: + ct.set_data_dir( ddtype="real" ) \ + .set_vasl_mod( vmod="random" ) + ) def get_available_ivfs(): """Get the PzKw IVF's available for selection.""" @@ -289,12 +291,13 @@ def test_html_names( webapp, webdriver, monkeypatch ): pytest.config.option.short_tests, #pylint: disable=no-member reason = "--short-tests specified" ) #pylint: disable=too-many-locals,too-many-branches -def test_common_vo( webapp, webdriver, monkeypatch ): +def test_common_vo( webapp, webdriver ): """Test loading of common vehicles/ordnance and landing craft.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - init_webapp( webapp, webdriver ) + init_webapp( webapp, webdriver, + reset = lambda ct: ct.set_data_dir( ddtype="real" ) + ) # initialize ALLIED_MINOR = [ "belgian", "danish", "dutch", "greek", "polish", "yugoslavian" ] @@ -303,7 +306,7 @@ def test_common_vo( webapp, webdriver, monkeypatch ): # get the common vehicles/ordnance def get_common_vo( fname ): """Get the vehicle/ordnance information from the specified file.""" - fname = os.path.join( REAL_DATA_DIR, fname ) + fname = os.path.join( DATA_DIR, fname ) data = json.load( open( fname, "r" ) ) def get_gpid( val ): #pylint: disable=missing-docstring if isinstance( val, list ): @@ -412,13 +415,15 @@ def test_common_vo( webapp, webdriver, monkeypatch ): not pytest.config.option.vasl_mods, #pylint: disable=no-member reason = "--vasl-mods not specified" ) #pylint: disable=too-many-statements -def test_vo_images( webapp, webdriver, monkeypatch ): #pylint: disable=too-many-statements +def test_vo_images( webapp, webdriver ): #pylint: disable=too-many-statements """Test handling of vehicles/ordnance that have multiple images.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - load_vasl_mod( REAL_DATA_DIR, monkeypatch ) - init_webapp( webapp, webdriver, scenario_persistence=1 ) + init_webapp( webapp, webdriver, scenario_persistence=1, + reset = lambda ct: + ct.set_data_dir( ddtype="real" ) \ + .set_vasl_mod( vmod="random" ) + ) def check_sortable2_entries( player_no, expected ): """Check the settings on the player's vehicles.""" @@ -587,13 +592,15 @@ def test_vo_images( webapp, webdriver, monkeypatch ): #pylint: disable=too-many- not pytest.config.option.vasl_mods, #pylint: disable=no-member reason = "--vasl-mods not specified" ) #pylint: disable=too-many-statements -def test_change_vo_image( webapp, webdriver, monkeypatch ): +def test_change_vo_image( webapp, webdriver ): """Test changing a V/O image.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - load_vasl_mod( REAL_DATA_DIR, monkeypatch ) - init_webapp( webapp, webdriver, scenario_persistence=1 ) + init_webapp( webapp, webdriver, scenario_persistence=1, + reset = lambda ct: + ct.set_data_dir( ddtype="real" ) \ + .set_vasl_mod( vmod="random" ) + ) # add an ISU-152 add_vo( webdriver, "vehicles", 2, "ISU-152 (AG)" ) @@ -659,13 +666,15 @@ def test_change_vo_image( webapp, webdriver, monkeypatch ): not pytest.config.option.vasl_mods, #pylint: disable=no-member reason = "--vasl-mods not specified" ) -def test_change_vo_image2( webapp, webdriver, monkeypatch ): +def test_change_vo_image2( webapp, webdriver ): """Test changing the image for a V/O that has no alternative images.""" # initialize - monkeypatch.setitem( webapp.config, "DATA_DIR", REAL_DATA_DIR ) - load_vasl_mod( REAL_DATA_DIR, monkeypatch ) - init_webapp( webapp, webdriver, scenario_persistence=1 ) + init_webapp( webapp, webdriver, scenario_persistence=1, + reset = lambda ct: + ct.set_data_dir( ddtype="real" ) \ + .set_vasl_mod( vmod="random" ) + ) # add an 107mm GVPM add_vo( webdriver, "ordnance", 2, "107mm GVPM obr. 38 (MTR)" ) diff --git a/vasl_templates/webapp/tests/utils.py b/vasl_templates/webapp/tests/utils.py index 86f24f9..8e20d29 100644 --- a/vasl_templates/webapp/tests/utils.py +++ b/vasl_templates/webapp/tests/utils.py @@ -6,8 +6,6 @@ import json import time import re import uuid -import glob -import random import pytest from PyQt5.QtWidgets import QApplication @@ -16,8 +14,7 @@ from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.action_chains import ActionChains from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException -from vasl_templates.webapp.file_server.vasl_mod import VaslMod -from vasl_templates.webapp import files as webapp_files +from vasl_templates.webapp.tests.remote import ControlTests # standard templates _STD_TEMPLATES = { @@ -44,29 +41,23 @@ def init_webapp( webapp, webdriver, **options ): global _webapp, _webdriver _webapp = webapp _webdriver = webdriver + # reset the server + # NOTE: We have to do this manually, since we can't use pytest's monkeypatch'ing, + # since we could be talking to a remote server (see ControlTests for more details). + control_tests = ControlTests( webapp ) + control_tests \ + .set_data_dir( ddtype="test" ) \ + .set_default_scenario( fname=None ) \ + .set_default_template_pack( dname=None ) \ + .set_vasl_mod( vmod=None ) \ + .set_vassal_engine( vengine=None ) + if "reset" in options: + options.pop( "reset" )( control_tests ) + webdriver.get( webapp.url_for( "main", **options ) ) wait_for( 5, lambda: find_child("#_page-loaded_") is not None ) -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -def load_vasl_mod( data_dir, monkeypatch, fname=None ): - """Load a VASL module.""" - - if data_dir: - vasl_mods_dir = pytest.config.option.vasl_mods #pylint: disable=no-member - if fname: - fname = os.path.join( vasl_mods_dir, fname ) - else: - # NOTE: Some tests require a VASL module to be loaded, and since they should all - # should behave in the same way, it doesn't matter which one we load. - fspec = os.path.join( vasl_mods_dir, "*.vmod" ) - fname = random.choice( glob.glob( fspec ) ) - vasl_mod = VaslMod( fname, data_dir ) - else: - vasl_mod = None - monkeypatch.setattr( webapp_files, "vasl_mod", vasl_mod ) - - return vasl_mod + return control_tests # --------------------------------------------------------------------- diff --git a/vasl_templates/webapp/vassal.py b/vasl_templates/webapp/vassal.py index 3702110..cf64438 100644 --- a/vasl_templates/webapp/vassal.py +++ b/vasl_templates/webapp/vassal.py @@ -216,7 +216,7 @@ def _parse_label_report( fname ): class VassalShim: """Provide access to VASSAL via the Java shim.""" - def __init__( self ): + def __init__( self ): #pylint: disable=too-many-branches # locate the VASSAL engine vassal_dir = app.config.get( "VASSAL_DIR" )