""" Test serving counter images. """ import os import io import json import re import shutil import urllib.request import pytest import tabulate from vasl_templates.webapp.vassal import SUPPORTED_VASSAL_VERSIONS from vasl_templates.webapp.vasl_mod import VaslMod, get_vo_gpids, compare_version_strings, SUPPORTED_VASL_MOD_VERSIONS from vasl_templates.webapp.config.constants import DATA_DIR from vasl_templates.webapp.vo import _kfw_listings #pylint: disable=protected-access from vasl_templates.webapp.utils import change_extn 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 _EXPECTED_MISSING_GPIDS_EXCEPTIONS = [ "6.5.0", "6.5.1", "6.6.0", "6.6.1" ] # --------------------------------------------------------------------- @pytest.mark.skipif( not pytest.config.option.vasl_mods, #pylint: disable=no-member reason = "--vasl-mods not specified" ) @pytest.mark.skipif( pytest.config.option.short_tests, #pylint: disable=no-member reason = "--short-tests specified" ) #pylint: disable=too-many-statements,too-many-locals def test_counter_images( webapp ): """Test that counter images are served correctly.""" # NOTE: This is ridiculously slow on Windows :-/ def check_images( gpids, check_front, check_back ): #pylint: disable=unused-argument """Check getting the front and back images for each counter.""" for gpid in gpids: for side in ("front","back"): url = webapp.url_for( "get_counter_image", gpid=gpid, side=side ) try: resp = urllib.request.urlopen( url ) resp_code = resp.code resp_data = resp.read() except urllib.error.HTTPError as ex: resp_code = ex.code resp_data = None assert locals()["check_"+side]( gpid, resp_code, resp_data ) # test counter images when no VASL module has been configured control_tests = ControlTests( webapp ) control_tests.set_vasl_mod( vmod=None ) # NOTE: It doesn't really matter which set of GPID's we use, since we're expecting # a missing image for everything anyway. We just use the most recent supported version. gpids = get_vo_gpids( None ) fname = os.path.join( os.path.split(__file__)[0], "../static/images/missing-image.png" ) missing_image_data = open( fname, "rb" ).read() check_images( gpids, check_front = lambda gpid,code,data: code == 200 and data == missing_image_data, check_back = lambda gpid,code,data: code == 200 and data == missing_image_data ) # FUDGE! 6.5.0 introduced a lot of new counters for K:FW. The vehicle/ordnance entries for these # will always be loaded, but if an older version of VASL has been configured, requests to get images # for these counters will, of course, fail, since the new counters won't be in the older VASL modules. # We figure out here what those GPID's are. # NOTE: All of this is horrendously complicated, and the problem will re-appear if new counters # are added to the core VASL module in the future. At that point, we should probably drop testing # against older versions of VASL and just test against the latest version :-/ expected_missing_gpids = set() for vo_type in ("vehicles","ordnance"): kfw_listings = _kfw_listings[ vo_type ] for entries in kfw_listings.values(): for entry in entries: if isinstance( entry["gpid"], list ): expected_missing_gpids.update( entry["gpid"] ) else: expected_missing_gpids.add( entry["gpid"] ) expected_missing_gpids = set( str(e) for e in expected_missing_gpids ) # NOTE: However, some of the GPID's used by the new K:FW counters use old images that are available # even in older versions of VASL, so we figure out here what those are. def get_gpids( fname ): """Extract the GPID's from the specified file.""" dname = os.path.join( os.path.split(__file__)[0], "fixtures" ) fname = os.path.join( dname, fname ) gpids = set() for line_buf in open(fname,"r"): mo = re.search( "^[0-9a-z:]+", line_buf ) if mo: gpids.add( mo.group() ) return gpids legacy_gpids = get_gpids( "vasl-pieces-legacy.txt" ) latest_gpids = get_gpids( "vasl-pieces-6.5.1.txt" ) common_gpids = legacy_gpids.intersection( latest_gpids ) expected_missing_gpids = expected_missing_gpids.difference( common_gpids ) expected_missing_gpids.remove( "1002" ) # FUDGE! this is a remapped GPID (11340) expected_missing_gpids.remove( "1527" ) # FUDGE! this is a remapped GPID (12730) def _do_check_front( gpid, code, data ): if vasl_version not in _EXPECTED_MISSING_GPIDS_EXCEPTIONS and gpid in expected_missing_gpids: return code == 404 and not data return code == 200 and data def _do_check_back( gpid, code, data ): if vasl_version not in _EXPECTED_MISSING_GPIDS_EXCEPTIONS and gpid in expected_missing_gpids: return code == 404 and not data return (code == 200 and data) or (code == 404 and not data) # initialize check_dir = os.path.join( os.path.split(__file__)[0], "fixtures" ) save_dir = os.environ.get( "COUNTERS_SAVEDIR" ) # nb: define this to save the generated reports if save_dir: if os.path.isdir( save_dir ): shutil.rmtree( save_dir ) os.makedirs( save_dir ) # test each VASL module file in the specified directory failed = False vmod_fnames = control_tests.get_vasl_mods() for vmod_fname in vmod_fnames: # install the VASL module file control_tests.set_vasl_mod( vmod=vmod_fname ) # 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( vmod_fname )[1] vasl_mods_dir = pytest.config.option.vasl_mods #pylint: disable=no-member fname = os.path.join( vasl_mods_dir, fname ) # figure out what we're expecting to see # NOTE: The results were the same across 6.4.0-6.4.4, but 6.5.0 introduced some changes. vasl_mod = VaslMod( fname, DATA_DIR, None ) vasl_version = vasl_mod.vasl_version fname = os.path.join( check_dir, "vasl-pieces-{}.txt".format( vasl_version ) ) if not os.path.isfile( fname ): fname = os.path.join( check_dir, "vasl-pieces-legacy.txt" ) expected_vasl_pieces = open( fname, "r" ).read() # generate a report for the pieces loaded buf = io.StringIO() _dump_pieces( vasl_mod, buf ) report = buf.getvalue() if save_dir: fname2 = change_extn( os.path.split(vmod_fname)[1], ".txt" ) with open( os.path.join(save_dir,fname2), "w" ) as fp: fp.write( report ) # check the report if report != expected_vasl_pieces: if save_dir: print( "FAILED:", vasl_version ) failed = True else: assert False, "Report mismatch: {}".format( vasl_version ) # check each counter gpids = get_vo_gpids( vasl_mod ) check_images( gpids, check_front=_do_check_front, check_back=_do_check_back ) assert not failed # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def _dump_pieces( vasl_mod, out ): """Dump the VaslMod pieces.""" # dump the VASL pieces results = [ [ "GPID", "Name", "Front images", "Back images"] ] pieces = vasl_mod._pieces #pylint: disable=protected-access # GPID's were originally int's but then changed to str's. We then started seeing non-numeric GPID's :-/ # For back-compat, we try to maintain sort order for numeric values. def sort_key( val ): #pylint: disable=missing-docstring if val.isdigit(): return ( "0"*10 + val )[-10:] else: # nb: we make sure that alphanumeric values appear after numeric values, even if they start with a number return "_" + val gpids = sorted( pieces.keys(), key=sort_key ) # nb: because GPID's changed from int to str :-/ for gpid in gpids: piece = pieces[ gpid ] assert piece["gpid"] == gpid results.append( [ gpid, piece["name"], piece["front_images"], piece["back_images"] ] ) print( tabulate.tabulate( results, headers="firstrow", numalign="left" ), file=out ) # --------------------------------------------------------------------- @pytest.mark.skipif( not pytest.config.option.vasl_mods, #pylint: disable=no-member reason = "--vasl-mods not specified" ) def test_gpid_remapping( webapp, webdriver ): """Test GPID remapping.""" # initialize control_tests = init_webapp( webapp, webdriver ) def check_gpid_image( gpid ): """Check if we can get the image for the specified GPID.""" url = webapp.url_for( "get_counter_image", gpid=gpid, side="front" ) try: resp = urllib.request.urlopen( url ) return resp.code except urllib.error.HTTPError as ex: assert ex.code != 200 return ex.code def check_entry( entry, url_stem, valid_image ): """Check a vehicle entry in the UI.""" img = find_child( "img.vasl-image", entry ) assert img.get_attribute( "src" ).endswith( url_stem ) mo = re.search( r"^/counter/(\d+)/", url_stem ) gpid = mo.group(1) if valid_image: assert check_gpid_image( gpid ) == 200 assert img.get_attribute( "width" ) == "47" # nb: this value depends on the CSS settings! else: assert check_gpid_image( gpid ) == 404 def do_test( vmod_fname, valid_images ): """Do the test.""" # initialize (using the specified VASL vmod) init_webapp( webapp, webdriver, scenario_persistence=1, reset = lambda ct: ct.set_data_dir( dtype="real" ) \ .set_vasl_mod( vmod=vmod_fname ) ) load_scenario( scenario_data ) # check that the German vehicles loaded correctly select_tab( "ob1" ) vehicles_sortable = find_child( "#ob_vehicles-sortable_1" ) entries = find_children( "li", vehicles_sortable ) assert len(entries) == 2 check_entry( entries[0], "/counter/2542/front", True ) check_entry( entries[1], "/counter/7124/front/0", valid_images ) # check that the American ordnance loaded correctly select_tab( "ob2" ) vehicles_sortable = find_child( "#ob_ordnance-sortable_2" ) entries = find_children( "li", vehicles_sortable ) assert len(entries) == 1 check_entry( entries[0], "/counter/879/front", valid_images ) # load the test scenario fname = os.path.join( os.path.split(__file__)[0], "fixtures/gpid-remapping.json" ) scenario_data = json.load( open( fname, "r" ) ) # locate the VASL modules vmod_fnames = control_tests.get_vasl_mods() def find_vasl_mod( version ): """Find the VASL module for the specified version.""" matches = [ fname for fname in vmod_fnames if "vasl-{}.vmod".format(version) in fname ] assert len(matches) == 1 return matches[0] # run the tests using VASL 6.4.4 and 6.5.0 # NOTE: Versions of VASL prior to 6.6.0 are no longer officially supported (since they use Java 8), # but we would still like to run these tests. See VassalShim._run_vassal_shim(), where we figure out # which version of Java to use, and run_vassal_tests() in test_vassal.py, where we check for invalid # combinations of VASSAL and VASL. Sigh... do_test( find_vasl_mod("6.4.4"), True ) do_test( find_vasl_mod("6.5.0"), True ) do_test( find_vasl_mod("6.5.1"), True ) # disable GPID remapping and try again prev_gpid_mappings = control_tests.set_gpid_remappings( gpids=[] ) try: do_test( find_vasl_mod("6.4.4"), True ) do_test( find_vasl_mod("6.5.0"), False ) do_test( find_vasl_mod("6.5.1"), 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 ) # --------------------------------------------------------------------- def test_compare_version_strings(): """Test comparing version strings.""" # test comparing VASSAL version strings for i,vassal_version in enumerate( SUPPORTED_VASSAL_VERSIONS): if i > 0: assert compare_version_strings( SUPPORTED_VASSAL_VERSIONS[i-1], vassal_version ) < 0 assert compare_version_strings( SUPPORTED_VASSAL_VERSIONS[i], vassal_version ) == 0 if i < len(SUPPORTED_VASSAL_VERSIONS)-1: assert compare_version_strings( vassal_version, SUPPORTED_VASSAL_VERSIONS[i+1] ) < 0 # test comparing VASL version strings for i,vasl_version in enumerate(SUPPORTED_VASL_MOD_VERSIONS): if i > 0: assert compare_version_strings( SUPPORTED_VASL_MOD_VERSIONS[i-1], vasl_version ) < 0 assert compare_version_strings( vasl_version, vasl_version ) == 0 if i < len(SUPPORTED_VASL_MOD_VERSIONS)-1: assert compare_version_strings( vasl_version, SUPPORTED_VASL_MOD_VERSIONS[i+1] ) < 0