def test_map_common_versionsuffixes(self): """Test mapping between two toolchain hierarchies""" test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') init_config(build_options={ 'robot_path': [test_easyconfigs], 'silent': True, 'valid_module_classes': module_classes(), }) get_toolchain_hierarchy.clear() gcc_binutils_tc = {'name': 'GCC', 'version': '4.9.3-2.26'} iccifort_binutils_tc = {'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25'} toolchain_mapping = map_toolchain_hierarchies(iccifort_binutils_tc, gcc_binutils_tc, self.modtool) possible_mappings = map_common_versionsuffixes('binutils', iccifort_binutils_tc, toolchain_mapping) self.assertEqual(possible_mappings, {'-binutils-2.25': '-binutils-2.26'}) # Make sure we only map upwards, here it's gzip 1.4 in gcc and 1.6 in iccifort possible_mappings = map_common_versionsuffixes('gzip', iccifort_binutils_tc, toolchain_mapping) self.assertEqual(possible_mappings, {}) # newer gzip is picked up other way around (GCC -> iccifort) toolchain_mapping = map_toolchain_hierarchies(gcc_binutils_tc, iccifort_binutils_tc, self.modtool) possible_mappings = map_common_versionsuffixes('gzip', gcc_binutils_tc, toolchain_mapping) self.assertEqual(possible_mappings, {'-gzip-1.4': '-gzip-1.6'})
def test_match_minimum_tc_specs(self): """Test matching a toolchain to lowest possible in a hierarchy""" test_easyconfigs = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') init_config( build_options={ 'robot_path': test_easyconfigs, 'silent': True, 'valid_module_classes': module_classes(), }) get_toolchain_hierarchy.clear() foss_hierarchy = get_toolchain_hierarchy( { 'name': 'foss', 'version': '2018a' }, incl_capabilities=True) iimpi_hierarchy = get_toolchain_hierarchy( { 'name': 'iimpi', 'version': '2016.01' }, incl_capabilities=True) # Hierarchies are returned with top-level toolchain last, foss has 4 elements here, intel has 2 self.assertEqual(foss_hierarchy[0]['name'], 'GCC') self.assertEqual(foss_hierarchy[1]['name'], 'golf') self.assertEqual(foss_hierarchy[2]['name'], 'gompi') self.assertEqual(foss_hierarchy[3]['name'], 'foss') self.assertEqual(iimpi_hierarchy[0]['name'], 'GCCcore') self.assertEqual(iimpi_hierarchy[1]['name'], 'iccifort') self.assertEqual(iimpi_hierarchy[2]['name'], 'iimpi') # base compiler first (GCCcore which maps to GCC/6.4.0-2.28) self.assertEqual( match_minimum_tc_specs(iimpi_hierarchy[0], foss_hierarchy), { 'name': 'GCC', 'version': '6.4.0-2.28' }) # then iccifort (which also maps to GCC/6.4.0-2.28) self.assertEqual( match_minimum_tc_specs(iimpi_hierarchy[1], foss_hierarchy), { 'name': 'GCC', 'version': '6.4.0-2.28' }) # Then MPI self.assertEqual( match_minimum_tc_specs(iimpi_hierarchy[2], foss_hierarchy), { 'name': 'gompi', 'version': '2018a' }) # Check against own math only subtoolchain for math self.assertEqual( match_minimum_tc_specs(foss_hierarchy[1], foss_hierarchy), { 'name': 'golf', 'version': '2018a' }) # Make sure there's an error when we can't do the mapping error_msg = "No possible mapping from source toolchain spec .*" self.assertErrorRegex(EasyBuildError, error_msg, match_minimum_tc_specs, foss_hierarchy[3], iimpi_hierarchy)
def test_check_capability_mapping(self): """Test comparing the functionality of two toolchains""" test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') init_config(build_options={ 'valid_module_classes': module_classes(), 'robot_path': test_easyconfigs, }) get_toolchain_hierarchy.clear() foss_hierarchy = get_toolchain_hierarchy({'name': 'foss', 'version': '2018a'}, incl_capabilities=True) iimpi_hierarchy = get_toolchain_hierarchy({'name': 'iimpi', 'version': '2016.01'}, incl_capabilities=True) # Hierarchies are returned with top-level toolchain last, foss has 4 elements here, intel has 2 self.assertEqual(foss_hierarchy[0]['name'], 'GCC') self.assertEqual(foss_hierarchy[1]['name'], 'golf') self.assertEqual(foss_hierarchy[2]['name'], 'gompi') self.assertEqual(foss_hierarchy[3]['name'], 'foss') self.assertEqual(iimpi_hierarchy[0]['name'], 'GCCcore') self.assertEqual(iimpi_hierarchy[1]['name'], 'iccifort') self.assertEqual(iimpi_hierarchy[2]['name'], 'iimpi') # golf <-> iimpi (should return False) self.assertFalse(check_capability_mapping(foss_hierarchy[1], iimpi_hierarchy[1]), "golf requires math libs") # gompi <-> iimpi self.assertTrue(check_capability_mapping(foss_hierarchy[2], iimpi_hierarchy[2])) # GCC <-> iimpi self.assertTrue(check_capability_mapping(foss_hierarchy[0], iimpi_hierarchy[2])) # GCC <-> iccifort self.assertTrue(check_capability_mapping(foss_hierarchy[0], iimpi_hierarchy[1])) # GCC <-> GCCcore self.assertTrue(check_capability_mapping(foss_hierarchy[0], iimpi_hierarchy[0]))
def test_map_easyconfig_to_target_tc_hierarchy(self): """Test mapping of easyconfig to target hierarchy""" test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') init_config(build_options={ 'robot_path': test_easyconfigs, 'silent': True, 'valid_module_classes': module_classes(), }) get_toolchain_hierarchy.clear() gcc_binutils_tc = {'name': 'GCC', 'version': '4.9.3-2.26'} iccifort_binutils_tc = {'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25'} # The below mapping includes a binutils mapping (2.26 to 2.25) tc_mapping = map_toolchain_hierarchies(gcc_binutils_tc, iccifort_binutils_tc, self.modtool) ec_spec = os.path.join(test_easyconfigs, 'h', 'hwloc', 'hwloc-1.6.2-GCC-4.9.3-2.26.eb') tweaked_spec = map_easyconfig_to_target_tc_hierarchy(ec_spec, tc_mapping) tweaked_ec = process_easyconfig(tweaked_spec)[0] tweaked_dict = tweaked_ec['ec'].asdict() # First check the mapped toolchain key, value = 'toolchain', iccifort_binutils_tc self.assertTrue(key in tweaked_dict and value == tweaked_dict[key]) # Also check that binutils has been mapped for key, value in {'name': 'binutils', 'version': '2.25', 'versionsuffix': ''}.items(): self.assertTrue(key in tweaked_dict['builddependencies'][0] and value == tweaked_dict['builddependencies'][0][key])
def test_match_minimum_tc_specs(self): """Test matching a toolchain to lowest possible in a hierarchy""" test_easyconfigs = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') init_config( build_options={ 'valid_module_classes': module_classes(), 'robot_path': test_easyconfigs, }) get_toolchain_hierarchy.clear() goolf_hierarchy = get_toolchain_hierarchy( { 'name': 'goolf', 'version': '1.4.10' }, incl_capabilities=True) iimpi_hierarchy = get_toolchain_hierarchy( { 'name': 'iimpi', 'version': '5.5.3-GCC-4.8.3' }, incl_capabilities=True) # Hierarchies are returned with top-level toolchain last, goolf has 4 elements here, intel has 2 self.assertEqual(goolf_hierarchy[0]['name'], 'GCC') self.assertEqual(goolf_hierarchy[1]['name'], 'golf') self.assertEqual(goolf_hierarchy[2]['name'], 'gompi') self.assertEqual(goolf_hierarchy[3]['name'], 'goolf') self.assertEqual(iimpi_hierarchy[0]['name'], 'iccifort') self.assertEqual(iimpi_hierarchy[1]['name'], 'iimpi') # Compiler first self.assertEqual( match_minimum_tc_specs(iimpi_hierarchy[0], goolf_hierarchy), { 'name': 'GCC', 'version': '4.7.2' }) # Then MPI self.assertEqual( match_minimum_tc_specs(iimpi_hierarchy[1], goolf_hierarchy), { 'name': 'gompi', 'version': '1.4.10' }) # Check against own math only subtoolchain for math self.assertEqual( match_minimum_tc_specs(goolf_hierarchy[1], goolf_hierarchy), { 'name': 'golf', 'version': '1.4.10' }) # Make sure there's an error when we can't do the mapping error_msg = "No possible mapping from source toolchain spec .*" self.assertErrorRegex(EasyBuildError, error_msg, match_minimum_tc_specs, goolf_hierarchy[3], iimpi_hierarchy)
def test_find_potential_version_mappings(self): """Test ability to find potential version mappings of a dependency for a given toolchain mapping""" test_easyconfigs = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') init_config( build_options={ 'robot_path': [test_easyconfigs], 'silent': True, 'valid_module_classes': module_classes(), }) get_toolchain_hierarchy.clear() gcc_binutils_tc = {'name': 'GCC', 'version': '4.9.3-2.26'} iccifort_binutils_tc = { 'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25' } # The below mapping includes a binutils mapping (2.26 to 2.25) tc_mapping = map_toolchain_hierarchies(gcc_binutils_tc, iccifort_binutils_tc, self.modtool) ec_spec = os.path.join(test_easyconfigs, 'h', 'hwloc', 'hwloc-1.6.2-GCC-4.9.3-2.26.eb') parsed_ec = process_easyconfig(ec_spec)[0] gzip_dep = [ dep for dep in parsed_ec['ec']['dependencies'] if dep['name'] == 'gzip' ][0] self.assertEqual(gzip_dep['full_mod_name'], 'gzip/1.4-GCC-4.9.3-2.26') potential_versions = find_potential_version_mappings( gzip_dep, tc_mapping) self.assertEqual(len(potential_versions), 1) # Should see version 1.6 of gzip with iccifort toolchain expected = { 'path': os.path.join(test_easyconfigs, 'g', 'gzip', 'gzip-1.6-iccifort-2016.1.150-GCC-4.9.3-2.25.eb'), 'toolchain': { 'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25' }, 'version': '1.6', } self.assertEqual(potential_versions[0], expected)
def test_list_deps_versionsuffixes(self): """Test listing of dependencies' version suffixes""" test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') build_options = { 'robot_path': [test_easyconfigs], 'silent': True, 'valid_module_classes': module_classes(), } init_config(build_options=build_options) get_toolchain_hierarchy.clear() ec_spec = os.path.join(test_easyconfigs, 'g', 'golf', 'golf-2018a.eb') self.assertEqual(list_deps_versionsuffixes(ec_spec), ['-serial']) ec_spec = os.path.join(test_easyconfigs, 't', 'toy', 'toy-0.0-deps.eb') self.assertEqual(list_deps_versionsuffixes(ec_spec), []) ec_spec = os.path.join(test_easyconfigs, 'g', 'gzip', 'gzip-1.4-GCC-4.6.3.eb') self.assertEqual(list_deps_versionsuffixes(ec_spec), ['-deps'])
def test_map_toolchain_hierarchies(self): """Test mapping between two toolchain hierarchies""" test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') init_config(build_options={ 'robot_path': test_easyconfigs, 'silent': True, 'valid_module_classes': module_classes(), }) get_toolchain_hierarchy.clear() foss_tc = {'name': 'foss', 'version': '2018a'} gompi_tc = {'name': 'gompi', 'version': '2018a'} iimpi_tc = {'name': 'iimpi', 'version': '2016.01'} # GCCcore is mapped to GCC, iccifort is mapped to GCC, iimpi is mapped to gompi expected = { 'GCCcore': {'name': 'GCC', 'version': '6.4.0-2.28'}, 'iccifort': {'name': 'GCC', 'version': '6.4.0-2.28'}, 'iimpi': {'name': 'gompi', 'version': '2018a'}, } self.assertEqual(map_toolchain_hierarchies(iimpi_tc, foss_tc, self.modtool), expected) # GCC is mapped to iccifort, gompi is mapped to iimpi expected = { 'GCC': {'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25'}, 'gompi': {'name': 'iimpi', 'version': '2016.01'} } self.assertEqual(map_toolchain_hierarchies(gompi_tc, iimpi_tc, self.modtool), expected) # Expect an error when there is no possible mapping error_msg = "No possible mapping from source toolchain spec .*" self.assertErrorRegex(EasyBuildError, error_msg, map_toolchain_hierarchies, foss_tc, iimpi_tc, self.modtool) # Test that we correctly include GCCcore binutils when it is there gcc_binutils_tc = {'name': 'GCC', 'version': '4.9.3-2.26'} iccifort_binutils_tc = {'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25'} # Should see a binutils in the mapping (2.26 will get mapped to 2.25) expected = { 'GCC': {'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25'}, 'GCCcore': {'name': 'GCCcore', 'version': '4.9.3'}, 'binutils': {'version': '2.25', 'versionsuffix': ''} } self.assertEqual(map_toolchain_hierarchies(gcc_binutils_tc, iccifort_binutils_tc, self.modtool), expected)
def test_map_easyconfig_to_target_tc_hierarchy(self): """Test mapping of easyconfig to target hierarchy""" test_easyconfigs = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') init_config( build_options={ 'robot_path': test_easyconfigs, 'silent': True, 'valid_module_classes': module_classes(), }) get_toolchain_hierarchy.clear() gcc_binutils_tc = {'name': 'GCC', 'version': '4.9.3-2.26'} iccifort_binutils_tc = { 'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25' } # The below mapping includes a binutils mapping (2.26 to 2.25) tc_mapping = map_toolchain_hierarchies(gcc_binutils_tc, iccifort_binutils_tc, self.modtool) ec_spec = os.path.join(test_easyconfigs, 'h', 'hwloc', 'hwloc-1.6.2-GCC-4.9.3-2.26.eb') tweaked_spec = map_easyconfig_to_target_tc_hierarchy( ec_spec, tc_mapping) tweaked_ec = process_easyconfig(tweaked_spec)[0] tweaked_dict = tweaked_ec['ec'].asdict() # First check the mapped toolchain key, value = 'toolchain', iccifort_binutils_tc self.assertTrue(key in tweaked_dict and value == tweaked_dict[key]) # Also check that binutils has been mapped for key, value in { 'name': 'binutils', 'version': '2.25', 'versionsuffix': '' }.items(): self.assertTrue( key in tweaked_dict['builddependencies'][0] and value == tweaked_dict['builddependencies'][0][key])
def test_match_minimum_tc_specs(self): """Test matching a toolchain to lowest possible in a hierarchy""" test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') init_config(build_options={ 'robot_path': test_easyconfigs, 'silent': True, 'valid_module_classes': module_classes(), }) get_toolchain_hierarchy.clear() foss_hierarchy = get_toolchain_hierarchy({'name': 'foss', 'version': '2018a'}, incl_capabilities=True) iimpi_hierarchy = get_toolchain_hierarchy({'name': 'iimpi', 'version': '2016.01'}, incl_capabilities=True) # Hierarchies are returned with top-level toolchain last, foss has 4 elements here, intel has 2 self.assertEqual(foss_hierarchy[0]['name'], 'GCC') self.assertEqual(foss_hierarchy[1]['name'], 'golf') self.assertEqual(foss_hierarchy[2]['name'], 'gompi') self.assertEqual(foss_hierarchy[3]['name'], 'foss') self.assertEqual(iimpi_hierarchy[0]['name'], 'GCCcore') self.assertEqual(iimpi_hierarchy[1]['name'], 'iccifort') self.assertEqual(iimpi_hierarchy[2]['name'], 'iimpi') # base compiler first (GCCcore which maps to GCC/6.4.0-2.28) self.assertEqual(match_minimum_tc_specs(iimpi_hierarchy[0], foss_hierarchy), {'name': 'GCC', 'version': '6.4.0-2.28'}) # then iccifort (which also maps to GCC/6.4.0-2.28) self.assertEqual(match_minimum_tc_specs(iimpi_hierarchy[1], foss_hierarchy), {'name': 'GCC', 'version': '6.4.0-2.28'}) # Then MPI self.assertEqual(match_minimum_tc_specs(iimpi_hierarchy[2], foss_hierarchy), {'name': 'gompi', 'version': '2018a'}) # Check against own math only subtoolchain for math self.assertEqual(match_minimum_tc_specs(foss_hierarchy[1], foss_hierarchy), {'name': 'golf', 'version': '2018a'}) # Make sure there's an error when we can't do the mapping error_msg = "No possible mapping from source toolchain spec .*" self.assertErrorRegex(EasyBuildError, error_msg, match_minimum_tc_specs, foss_hierarchy[3], iimpi_hierarchy)
def test_get_toolchain_hierarchy(self): """Test get_toolchain_hierarchy function.""" test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') init_config(build_options={ 'valid_module_classes': module_classes(), 'robot_path': test_easyconfigs, }) goolf_hierarchy = get_toolchain_hierarchy({'name': 'goolf', 'version': '1.4.10'}) self.assertEqual(goolf_hierarchy, [ {'name': 'GCC', 'version': '4.7.2'}, {'name': 'gompi', 'version': '1.4.10'}, {'name': 'goolf', 'version': '1.4.10'}, ]) iimpi_hierarchy = get_toolchain_hierarchy({'name': 'iimpi', 'version': '5.5.3-GCC-4.8.3'}) self.assertEqual(iimpi_hierarchy, [ {'name': 'iccifort', 'version': '2013.5.192-GCC-4.8.3'}, {'name': 'iimpi', 'version': '5.5.3-GCC-4.8.3'}, ]) # test also including dummy init_config(build_options={ 'add_dummy_to_minimal_toolchains': True, 'valid_module_classes': module_classes(), 'robot_path': test_easyconfigs, }) get_toolchain_hierarchy.clear() gompi_hierarchy = get_toolchain_hierarchy({'name': 'gompi', 'version': '1.4.10'}) self.assertEqual(gompi_hierarchy, [ {'name': 'dummy', 'version': ''}, {'name': 'GCC', 'version': '4.7.2'}, {'name': 'gompi', 'version': '1.4.10'}, ]) get_toolchain_hierarchy.clear() # check whether GCCcore is considered as subtoolchain, even if it's only listed as a dep gcc_hierarchy = get_toolchain_hierarchy({'name': 'GCC', 'version': '4.9.3-2.25'}) self.assertEqual(gcc_hierarchy, [ {'name': 'dummy', 'version': ''}, {'name': 'GCCcore', 'version': '4.9.3'}, {'name': 'GCC', 'version': '4.9.3-2.25'}, ]) get_toolchain_hierarchy.clear() iccifort_hierarchy = get_toolchain_hierarchy({'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25'}) self.assertEqual(iccifort_hierarchy, [ {'name': 'dummy', 'version': ''}, {'name': 'GCCcore', 'version': '4.9.3'}, {'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25'}, ])
def test_find_potential_version_mappings(self): """Test ability to find potential version mappings of a dependency for a given toolchain mapping""" test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') init_config(build_options={ 'robot_path': [test_easyconfigs], 'silent': True, 'valid_module_classes': module_classes(), }) get_toolchain_hierarchy.clear() gcc_binutils_tc = {'name': 'GCC', 'version': '4.9.3-2.26'} iccifort_binutils_tc = {'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25'} # The below mapping includes a binutils mapping (2.26 to 2.25) tc_mapping = map_toolchain_hierarchies(gcc_binutils_tc, iccifort_binutils_tc, self.modtool) ec_spec = os.path.join(test_easyconfigs, 'h', 'hwloc', 'hwloc-1.6.2-GCC-4.9.3-2.26.eb') parsed_ec = process_easyconfig(ec_spec)[0] gzip_dep = [dep for dep in parsed_ec['ec']['dependencies'] if dep['name'] == 'gzip'][0] self.assertEqual(gzip_dep['full_mod_name'], 'gzip/1.4-GCC-4.9.3-2.26') potential_versions = find_potential_version_mappings(gzip_dep, tc_mapping) self.assertEqual(len(potential_versions), 1) # Should see version 1.6 of gzip with iccifort toolchain expected = { 'path': os.path.join(test_easyconfigs, 'g', 'gzip', 'gzip-1.6-iccifort-2016.1.150-GCC-4.9.3-2.25.eb'), 'toolchain': {'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25'}, 'version': '1.6', 'versionsuffix': '', } self.assertEqual(potential_versions[0], expected) # Test that we can override respecting the versionsuffix # Create toolchain mapping for OpenBLAS gcc_4_tc = {'name': 'GCC', 'version': '4.8.2'} gcc_6_tc = {'name': 'GCC', 'version': '6.4.0-2.28'} tc_mapping = map_toolchain_hierarchies(gcc_4_tc, gcc_6_tc, self.modtool) # Create a dep with the necessary params (including versionsuffix) openblas_dep = { 'toolchain': {'version': '4.8.2', 'name': 'GCC'}, 'name': 'OpenBLAS', 'system': False, 'versionsuffix': '-LAPACK-3.4.2', 'version': '0.2.8' } self.mock_stderr(True) potential_versions = find_potential_version_mappings(openblas_dep, tc_mapping) errtxt = self.get_stderr() warning_stub = "\nWARNING: There may be newer version(s) of dep 'OpenBLAS' available with a different " \ "versionsuffix to '-LAPACK-3.4.2'" self.mock_stderr(False) self.assertTrue(errtxt.startswith(warning_stub)) self.assertEqual(len(potential_versions), 0) potential_versions = find_potential_version_mappings(openblas_dep, tc_mapping, ignore_versionsuffixes=True) self.assertEqual(len(potential_versions), 1) expected = { 'path': os.path.join(test_easyconfigs, 'o', 'OpenBLAS', 'OpenBLAS-0.2.20-GCC-6.4.0-2.28.eb'), 'toolchain': {'version': '6.4.0-2.28', 'name': 'GCC'}, 'version': '0.2.20', 'versionsuffix': '', } self.assertEqual(potential_versions[0], expected)
def test_resolve_dependencies_minimal(self): """Test resolved dependencies with minimal toolchain.""" # replace log.experimental with log.warning to allow experimental code easybuild.framework.easyconfig.tools._log.experimental = easybuild.framework.easyconfig.tools._log.warning test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') self.install_mock_module() init_config(build_options={ 'allow_modules_tool_mismatch': True, 'minimal_toolchains': True, 'use_existing_modules': True, 'external_modules_metadata': ConfigObj(), 'robot_path': test_easyconfigs, 'valid_module_classes': module_classes(), 'validate': False, }) barec = os.path.join(self.test_prefix, 'bar-1.2.3-goolf-1.4.10.eb') barec_lines = [ "easyblock = 'ConfigureMake'", "name = 'bar'", "version = '1.2.3'", "homepage = 'http://example.com'", "description = 'foo'", # deliberately listing components of toolchain as dependencies without specifying subtoolchains, # to test resolving of dependencies with minimal toolchain # for each of these, we know test easyconfigs are available (which are required here) "dependencies = [", " ('OpenMPI', '1.6.4'),", # available with GCC/4.7.2 " ('OpenBLAS', '0.2.6', '-LAPACK-3.4.2'),", # available with gompi/1.4.10 " ('ScaLAPACK', '2.0.2', '-OpenBLAS-0.2.6-LAPACK-3.4.2'),", # available with gompi/1.4.10 " ('SQLite', '3.8.10.2'),", "]", # toolchain as list line, for easy modification later; # the use of %(version_major)s here is mainly to check if templates are being handled correctly # (it doesn't make much sense, but it serves the purpose) "toolchain = {'name': 'goolf', 'version': '%(version_major)s.4.10'}", ] write_file(barec, '\n'.join(barec_lines)) bar = process_easyconfig(barec)[0] # all modules in the dep graph, in order all_mods_ordered = [ 'GCC/4.7.2', 'hwloc/1.6.2-GCC-4.7.2', 'OpenMPI/1.6.4-GCC-4.7.2', 'gompi/1.4.10', 'OpenBLAS/0.2.6-gompi-1.4.10-LAPACK-3.4.2', 'ScaLAPACK/2.0.2-gompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2', 'SQLite/3.8.10.2-GCC-4.7.2', 'FFTW/3.3.3-gompi-1.4.10', 'goolf/1.4.10', 'bar/1.2.3-goolf-1.4.10', ] # no modules available, so all dependencies are retained MockModule.avail_modules = [] res = resolve_dependencies([bar], self.modtool) self.assertEqual(len(res), 10) self.assertEqual([x['full_mod_name'] for x in res], all_mods_ordered) MockModule.avail_modules = [ 'GCC/4.7.2', 'gompi/1.4.10', 'goolf/1.4.10', 'OpenMPI/1.6.4-GCC-4.7.2', 'OpenBLAS/0.2.6-gompi-1.4.10-LAPACK-3.4.2', 'ScaLAPACK/2.0.2-gompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2', 'SQLite/3.8.10.2-GCC-4.7.2', ] # test resolving dependencies with minimal toolchain (rather than using goolf/1.4.10 for all of them) # existing modules are *not* taken into account when determining minimal subtoolchain, by default res = resolve_dependencies([bar], self.modtool) self.assertEqual(len(res), 1) self.assertEqual(res[0]['full_mod_name'], bar['ec'].full_mod_name) # test retaining all dependencies, regardless of whether modules are available or not res = resolve_dependencies([bar], self.modtool, retain_all_deps=True) self.assertEqual(len(res), 10) mods = [x['full_mod_name'] for x in res] self.assertEqual(mods, all_mods_ordered) self.assertTrue('SQLite/3.8.10.2-GCC-4.7.2' in mods) # test taking into account existing modules # with an SQLite module with goolf/1.4.10 in place, this toolchain should be used rather than GCC/4.7.2 MockModule.avail_modules = [ 'SQLite/3.8.10.2-goolf-1.4.10', ] # parsed easyconfigs are cached, so clear the cache before reprocessing easyconfigs ecec._easyconfigs_cache.clear() bar = process_easyconfig(barec)[0] res = resolve_dependencies([bar], self.modtool, retain_all_deps=True) self.assertEqual(len(res), 10) mods = [x['full_mod_name'] for x in res] self.assertTrue('SQLite/3.8.10.2-goolf-1.4.10' in mods) self.assertFalse('SQLite/3.8.10.2-GCC-4.7.2' in mods) # Check whether having 2 version of dummy toolchain is ok # Clear easyconfig and toolchain caches ecec._easyconfigs_cache.clear() get_toolchain_hierarchy.clear() init_config(build_options={ 'allow_modules_tool_mismatch': True, 'minimal_toolchains': True, 'add_dummy_to_minimal_toolchains': True, 'external_modules_metadata': ConfigObj(), 'robot_path': test_easyconfigs, 'valid_module_classes': module_classes(), 'validate': False, }) impi_txt = read_file(os.path.join(test_easyconfigs, 'i', 'impi', 'impi-4.1.3.049.eb')) self.assertTrue(re.search("^toolchain = {'name': 'dummy', 'version': ''}", impi_txt, re.M)) gzip_txt = read_file(os.path.join(test_easyconfigs, 'g', 'gzip', 'gzip-1.4.eb')) self.assertTrue(re.search("^toolchain = {'name': 'dummy', 'version': 'dummy'}", gzip_txt, re.M)) barec = os.path.join(self.test_prefix, 'bar-1.2.3-goolf-1.4.10.eb') barec_lines = [ "easyblock = 'ConfigureMake'", "name = 'bar'", "version = '1.2.3'", "homepage = 'http://example.com'", "description = 'foo'", # deliberately listing components of toolchain as dependencies without specifying subtoolchains, # to test resolving of dependencies with minimal toolchain # for each of these, we know test easyconfigs are available (which are required here) "dependencies = [", " ('impi', '4.1.3.049'),", # has toolchain ('dummy', '') " ('gzip', '1.4'),", # has toolchain ('dummy', 'dummy') "]", # toolchain as list line, for easy modification later "toolchain = {'name': 'goolf', 'version': '1.4.10'}", ] write_file(barec, '\n'.join(barec_lines)) bar = process_easyconfig(barec)[0] res = resolve_dependencies([bar], self.modtool, retain_all_deps=True) self.assertEqual(len(res), 11) mods = [x['full_mod_name'] for x in res] self.assertTrue('impi/4.1.3.049' in mods) self.assertTrue('gzip/1.4' in mods)
def test_get_toolchain_hierarchy(self): """Test get_toolchain_hierarchy function.""" test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') init_config(build_options={ 'valid_module_classes': module_classes(), 'robot_path': test_easyconfigs, }) goolf_hierarchy = get_toolchain_hierarchy({'name': 'goolf', 'version': '1.4.10'}) self.assertEqual(goolf_hierarchy, [ {'name': 'GCC', 'version': '4.7.2'}, {'name': 'gompi', 'version': '1.4.10'}, {'name': 'goolf', 'version': '1.4.10'}, ]) iimpi_hierarchy = get_toolchain_hierarchy({'name': 'iimpi', 'version': '5.5.3-GCC-4.8.3'}) self.assertEqual(iimpi_hierarchy, [ {'name': 'iccifort', 'version': '2013.5.192-GCC-4.8.3'}, {'name': 'iimpi', 'version': '5.5.3-GCC-4.8.3'}, ]) # test also including dummy init_config(build_options={ 'add_dummy_to_minimal_toolchains': True, 'valid_module_classes': module_classes(), 'robot_path': test_easyconfigs, }) get_toolchain_hierarchy.clear() gompi_hierarchy = get_toolchain_hierarchy({'name': 'gompi', 'version': '1.4.10'}) self.assertEqual(gompi_hierarchy, [ {'name': 'dummy', 'version': ''}, {'name': 'GCC', 'version': '4.7.2'}, {'name': 'gompi', 'version': '1.4.10'}, ]) get_toolchain_hierarchy.clear() # check whether GCCcore is considered as subtoolchain, even if it's only listed as a dep gcc_hierarchy = get_toolchain_hierarchy({'name': 'GCC', 'version': '4.9.3-2.25'}) self.assertEqual(gcc_hierarchy, [ {'name': 'dummy', 'version': ''}, {'name': 'GCCcore', 'version': '4.9.3'}, {'name': 'GCC', 'version': '4.9.3-2.25'}, ]) get_toolchain_hierarchy.clear() iccifort_hierarchy = get_toolchain_hierarchy({'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25'}) self.assertEqual(iccifort_hierarchy, [ {'name': 'dummy', 'version': ''}, {'name': 'GCCcore', 'version': '4.9.3'}, {'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25'}, ]) get_toolchain_hierarchy.clear() build_options = { 'add_dummy_to_minimal_toolchains': True, 'external_modules_metadata': ConfigObj(), 'robot_path': test_easyconfigs, } init_config(build_options=build_options) craycce_hierarchy = get_toolchain_hierarchy({'name': 'CrayCCE', 'version': '5.1.29'}) self.assertEqual(craycce_hierarchy, [ {'name': 'dummy', 'version': ''}, {'name': 'CrayCCE', 'version': '5.1.29'}, ])
def test_map_easyconfig_to_target_tc_hierarchy(self): """Test mapping of easyconfig to target hierarchy""" test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') build_options = { 'robot_path': [test_easyconfigs], 'silent': True, 'valid_module_classes': module_classes(), } init_config(build_options=build_options) get_toolchain_hierarchy.clear() gcc_binutils_tc = {'name': 'GCC', 'version': '4.9.3-2.26'} iccifort_binutils_tc = {'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25'} # The below mapping includes a binutils mapping (2.26 to 2.25) tc_mapping = map_toolchain_hierarchies(gcc_binutils_tc, iccifort_binutils_tc, self.modtool) ec_spec = os.path.join(test_easyconfigs, 'h', 'hwloc', 'hwloc-1.6.2-GCC-4.9.3-2.26.eb') tweaked_spec = map_easyconfig_to_target_tc_hierarchy(ec_spec, tc_mapping) tweaked_ec = process_easyconfig(tweaked_spec)[0] tweaked_dict = tweaked_ec['ec'].asdict() # First check the mapped toolchain key, value = 'toolchain', iccifort_binutils_tc self.assertTrue(key in tweaked_dict and value == tweaked_dict[key]) # Also check that binutils has been mapped for key, value in {'name': 'binutils', 'version': '2.25', 'versionsuffix': ''}.items(): self.assertTrue(key in tweaked_dict['builddependencies'][0] and value == tweaked_dict['builddependencies'][0][key]) # Now test the case where we try to update the dependencies init_config(build_options=build_options) get_toolchain_hierarchy.clear() tweaked_spec = map_easyconfig_to_target_tc_hierarchy(ec_spec, tc_mapping, update_dep_versions=True) tweaked_ec = process_easyconfig(tweaked_spec)[0] tweaked_dict = tweaked_ec['ec'].asdict() # First check the mapped toolchain key, value = 'toolchain', iccifort_binutils_tc self.assertTrue(key in tweaked_dict and value == tweaked_dict[key]) # Also check that binutils has been mapped for key, value in {'name': 'binutils', 'version': '2.25', 'versionsuffix': ''}.items(): self.assertTrue( key in tweaked_dict['builddependencies'][0] and value == tweaked_dict['builddependencies'][0][key] ) # Also check that the gzip dependency was upgraded for key, value in {'name': 'gzip', 'version': '1.6', 'versionsuffix': ''}.items(): self.assertTrue(key in tweaked_dict['dependencies'][0] and value == tweaked_dict['dependencies'][0][key]) # Make sure there are checksums for our next test self.assertTrue(tweaked_dict['checksums']) # Test the case where we also update the software version at the same time init_config(build_options=build_options) get_toolchain_hierarchy.clear() new_version = '1.x.3' tweaked_spec = map_easyconfig_to_target_tc_hierarchy(ec_spec, tc_mapping, update_build_specs={'version': new_version}, update_dep_versions=True) tweaked_ec = process_easyconfig(tweaked_spec)[0] tweaked_dict = tweaked_ec['ec'].asdict() # First check the mapped toolchain key, value = 'toolchain', iccifort_binutils_tc self.assertTrue(key in tweaked_dict and value == tweaked_dict[key]) # Also check that binutils has been mapped for key, value in {'name': 'binutils', 'version': '2.25', 'versionsuffix': ''}.items(): self.assertTrue( key in tweaked_dict['builddependencies'][0] and value == tweaked_dict['builddependencies'][0][key] ) # Also check that the gzip dependency was upgraded for key, value in {'name': 'gzip', 'version': '1.6', 'versionsuffix': ''}.items(): self.assertTrue(key in tweaked_dict['dependencies'][0] and value == tweaked_dict['dependencies'][0][key]) # Finally check that the version was upgraded key, value = 'version', new_version self.assertTrue(key in tweaked_dict and value == tweaked_dict[key]) # and that the checksum was removed self.assertFalse(tweaked_dict['checksums']) # Check that if we update a software version, it also updates the version if the software appears in an # extension list (like for a PythonBundle) ec_spec = os.path.join(test_easyconfigs, 't', 'toy', 'toy-0.0-gompi-2018a-test.eb') # Create the trivial toolchain mapping toolchain = {'name': 'gompi', 'version': '2018a'} tc_mapping = map_toolchain_hierarchies(toolchain, toolchain, self.modtool) # Update the software version init_config(build_options=build_options) get_toolchain_hierarchy.clear() new_version = '1.x.3' tweaked_spec = map_easyconfig_to_target_tc_hierarchy(ec_spec, tc_mapping, update_build_specs={'version': new_version}, update_dep_versions=False) tweaked_ec = process_easyconfig(tweaked_spec)[0] extensions = tweaked_ec['ec']['exts_list'] # check one extension with the same name exists and that the version has been updated hit_extension = 0 for extension in extensions: if isinstance(extension, tuple) and extension[0] == 'toy': self.assertEqual(extension[1], new_version) # Make sure checksum has been purged self.assertFalse('checksums' in extension[2]) hit_extension += 1 self.assertEqual(hit_extension, 1, "Should only have updated one extension")
def test_robot_find_minimal_toolchain_of_dependency(self): """Test robot_find_minimal_toolchain_of_dependency.""" # replace log.experimental with log.warning to allow experimental code easybuild.framework.easyconfig.tools._log.experimental = easybuild.framework.easyconfig.tools._log.warning test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') init_config(build_options={ 'valid_module_classes': module_classes(), 'robot_path': test_easyconfigs, }) # # First test that it can do basic resolution # gzip15 = { 'name': 'gzip', 'version': '1.5', 'versionsuffix': '', 'toolchain': {'name': 'goolf', 'version': '1.4.10'}, } get_toolchain_hierarchy.clear() new_gzip15_toolchain = robot_find_minimal_toolchain_of_dependency(gzip15, self.modtool) self.assertEqual(new_gzip15_toolchain, gzip15['toolchain']) # no easyconfig for gzip 1.4 with matching non-dummy (sub)toolchain gzip14 = { 'name': 'gzip', 'version': '1.4', 'versionsuffix': '', 'toolchain': {'name': 'goolf', 'version': '1.4.10'}, } get_toolchain_hierarchy.clear() self.assertEqual(robot_find_minimal_toolchain_of_dependency(gzip14, self.modtool), None) gzip14['toolchain'] = {'name': 'gompi', 'version': '1.4.10'} # # Second test also including dummy toolchain # init_config(build_options={ 'add_dummy_to_minimal_toolchains': True, 'valid_module_classes': module_classes(), 'robot_path': test_easyconfigs, }) # specify alternative parent toolchain gompi_1410 = {'name': 'gompi', 'version': '1.4.10'} get_toolchain_hierarchy.clear() new_gzip14_toolchain = robot_find_minimal_toolchain_of_dependency(gzip14, self.modtool, parent_tc=gompi_1410) self.assertTrue(new_gzip14_toolchain != gzip14['toolchain']) self.assertEqual(new_gzip14_toolchain, {'name': 'dummy', 'version': ''}) # default: use toolchain from dependency gzip14['toolchain'] = gompi_1410 get_toolchain_hierarchy.clear() new_gzip14_toolchain = robot_find_minimal_toolchain_of_dependency(gzip14, self.modtool) self.assertTrue(new_gzip14_toolchain != gzip14['toolchain']) self.assertEqual(new_gzip14_toolchain, {'name': 'dummy', 'version': ''}) # check reversed order (parent tc first) and skipping of parent tc itself dep = { 'name': 'SQLite', 'version': '3.8.10.2', 'toolchain': {'name': 'goolf', 'version': '1.4.10'}, } res = robot_find_minimal_toolchain_of_dependency(dep, self.modtool) self.assertEqual(res, {'name': 'GCC', 'version': '4.7.2'}) res = robot_find_minimal_toolchain_of_dependency(dep, self.modtool, parent_first=True) self.assertEqual(res, {'name': 'goolf', 'version': '1.4.10'}) # # Finally test if it can recognise existing modules and use those # barec = os.path.join(self.test_prefix, 'bar-1.2.3-goolf-1.4.10.eb') barec_txt = '\n'.join([ "easyblock = 'ConfigureMake'", "name = 'bar'", "version = '1.2.3'", "homepage = 'http://example.com'", "description = 'foo'", "toolchain = {'name': 'goolf', 'version': '1.4.10'}", # deliberately listing components of toolchain as dependencies without specifying subtoolchains, # to test resolving of dependencies with minimal toolchain # for each of these, we know test easyconfigs are available (which are required here) "dependencies = [", " ('OpenMPI', '1.6.4'),", # available with GCC/4.7.2 " ('OpenBLAS', '0.2.6', '-LAPACK-3.4.2'),", # available with gompi/1.4.10 " ('ScaLAPACK', '2.0.2', '-OpenBLAS-0.2.6-LAPACK-3.4.2'),", # available with gompi/1.4.10 " ('SQLite', '3.8.10.2'),", # available with goolf/1.4.10, gompi/1.4.10 and GCC/4.7.2 "]", ]) write_file(barec, barec_txt) # check without --minimal-toolchains init_config(build_options={ 'valid_module_classes': module_classes(), 'robot_path': test_easyconfigs, }) bar = EasyConfig(barec) expected_dep_versions = [ '1.6.4-GCC-4.7.2', '0.2.6-gompi-1.4.10-LAPACK-3.4.2', '2.0.2-gompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2', '3.8.10.2-goolf-1.4.10', ] for dep, expected_dep_version in zip(bar.dependencies(), expected_dep_versions): self.assertEqual(det_full_ec_version(dep), expected_dep_version) # check with --minimal-toolchains enabled init_config(build_options={ 'minimal_toolchains': True, 'valid_module_classes': module_classes(), 'robot_path': test_easyconfigs, }) bar = EasyConfig(barec) # check that all bar dependencies have been processed as expected expected_dep_versions[-1] = '3.8.10.2-GCC-4.7.2' for dep, expected_dep_version in zip(bar.dependencies(), expected_dep_versions): self.assertEqual(det_full_ec_version(dep), expected_dep_version) # Add the gompi/1.4.10 version of SQLite as an available module module_parent = os.path.join(self.test_prefix, 'minimal_toolchain_modules') module_file = os.path.join(module_parent, 'SQLite', '3.8.10.2-gompi-1.4.10') module_txt = '\n'.join([ "#%Module", "set root /tmp/SQLite/3.8.10.2", "setenv EBROOTSQLITE $root", "setenv EBVERSIONSQLITE 3.8.10.2", "setenv EBDEVELSQLITE $root/easybuild/SQLite-3.8.10.2-easybuild-devel", ]) write_file(module_file, module_txt) os.environ['MODULEPATH'] = module_parent # Add the parent directory to the MODULEPATH invalidate_module_caches_for(module_parent) # Reinitialize the environment for the updated MODULEPATH and use_existing_modules init_config(build_options={ 'minimal_toolchains': True, 'use_existing_modules': True, 'valid_module_classes': module_classes(), 'robot_path': test_easyconfigs, }) # Check gompi is now being picked up bar = EasyConfig(barec) # Re-parse the parent easyconfig sqlite = bar.dependencies()[3] self.assertEqual(det_full_ec_version(sqlite), '3.8.10.2-gompi-1.4.10') # Add the goolf version as an available version and check that gets precedence over the gompi version module_file = os.path.join(module_parent, 'SQLite', '3.8.10.2-goolf-1.4.10') write_file(module_file, module_txt) invalidate_module_caches_for(module_parent) bar = EasyConfig(barec) # Re-parse the parent easyconfig sqlite = bar.dependencies()[3] self.assertEqual(det_full_ec_version(sqlite), '3.8.10.2-goolf-1.4.10')