def test_index_record(self): dst = Dist('defaults::foo-1.2.3-4.tar.bz2') rec = DPkg(dst) a = MatchSpec(dst) b = MatchSpec(rec) assert b.match(rec) assert a.match(rec)
def test_index_record(self): dst = Dist('defaults::foo-1.2.3-4.tar.bz2') rec = DPkg(dst) a = MatchSpec(dst) b = MatchSpec(rec) assert b.match(rec.dump()) assert b.match(rec) assert a.match(rec)
def test_features(self): dst = Dist('defaults::foo-1.2.3-4.tar.bz2') a = MatchSpec(features='test') assert a.match(DPkg(dst, features='test')) assert not a.match(DPkg(dst, features='test2')) assert a.match(DPkg(dst, features='test me')) assert a.match(DPkg(dst, features='you test')) assert a.match(DPkg(dst, features='you test me')) assert a.exact_field('features') == 'test'
def version_matches_spec(spec_string, version=open_ce_version): ''' Uses conda version specification syntax to check if version matches spec_string. e.g. version_matches_spec(">=1.2,<1.3", "1.2.1") -> True version_matches_spec(">=1.2,<1.3", "1.3.0") -> False ''' match_spec = MatchSpec("test[version='{}']".format(spec_string)) query_pkg = {"name": "test", "version": version, "build": "", "build_number": 0} return match_spec.match(query_pkg)
def test_match(self): for spec, result in [ ('numpy 1.7*', True), ('numpy 1.7.1', True), ('numpy 1.7', False), ('numpy 1.5*', False), ('numpy >=1.5', True), ('numpy >=1.5,<2', True), ('numpy >=1.8,<1.9', False), ('numpy >1.5,<2,!=1.7.1', False), ('numpy >1.8,<2|==1.7', False),('numpy >1.8,<2|>=1.7.1', True), ('numpy >=1.8|1.7*', True), ('numpy ==1.7', False), ('numpy >=1.5,>1.6', True), ('numpy ==1.7.1', True), ('numpy >=1,*.7.*', True), ('numpy *.7.*,>=1', True), ('numpy >=1,*.8.*', False), ('numpy >=2,*.7.*', False), ('numpy 1.6*|1.7*', True), ('numpy 1.6*|1.8*', False), ('numpy 1.6.2|1.7*', True), ('numpy 1.6.2|1.7.1', True), ('numpy 1.6.2|1.7.0', False), ('numpy 1.7.1 py27_0', True), ('numpy 1.7.1 py26_0', False), ('numpy >1.7.1a', True), ('python', False), ]: m = MatchSpec(spec) assert m.match(DPkg('numpy-1.7.1-py27_0.tar.bz2')) == result assert 'name' in m assert m.name == 'python' or 'version' in m # both version numbers conforming to PEP 440 assert not MatchSpec('numpy >=1.0.1').match(DPkg('numpy-1.0.1a-0.tar.bz2')) # both version numbers non-conforming to PEP 440 assert not MatchSpec('numpy >=1.0.1.vc11').match(DPkg('numpy-1.0.1a.vc11-0.tar.bz2')) assert MatchSpec('numpy >=1.0.1*.vc11').match(DPkg('numpy-1.0.1a.vc11-0.tar.bz2')) # one conforming, other non-conforming to PEP 440 assert MatchSpec('numpy <1.0.1').match(DPkg('numpy-1.0.1.vc11-0.tar.bz2')) assert MatchSpec('numpy <1.0.1').match(DPkg('numpy-1.0.1a.vc11-0.tar.bz2')) assert not MatchSpec('numpy >=1.0.1.vc11').match(DPkg('numpy-1.0.1a-0.tar.bz2')) assert MatchSpec('numpy >=1.0.1a').match(DPkg('numpy-1.0.1z-0.tar.bz2')) assert MatchSpec('numpy >=1.0.1a py27*').match(DPkg('numpy-1.0.1z-py27_1.tar.bz2')) assert MatchSpec('blas * openblas_0').match(DPkg('blas-1.0-openblas_0.tar.bz2')) assert MatchSpec('blas').is_simple() assert not MatchSpec('blas').is_exact() assert not MatchSpec('blas 1.0').is_simple() assert not MatchSpec('blas 1.0').is_exact() assert not MatchSpec('blas 1.0 1').is_simple() assert not MatchSpec('blas 1.0 1').is_exact() assert not MatchSpec('blas 1.0 *').is_exact() assert MatchSpec(Dist('blas-1.0-openblas_0.tar.bz2')).is_exact() assert MatchSpec(fn='blas-1.0-openblas_0.tar.bz2', schannel='defaults').is_exact() m = MatchSpec('blas 1.0', optional=True) m2 = MatchSpec(m, optional=False) m3 = MatchSpec(m2, target='blas-1.0-0.tar.bz2') m4 = MatchSpec(m3, target=None, optional=True) assert m.spec == m2.spec and m.optional != m2.optional assert m2.spec == m3.spec and m2.optional == m3.optional and m2.target != m3.target assert m == m4 self.assertRaises(ValueError, MatchSpec, (1, 2, 3))
def test_dist(self): with env_unmodified(conda_tests_ctxt_mgmt_def_pol): dst = Dist('defaults::foo-1.2.3-4.tar.bz2') a = MatchSpec(dst) b = MatchSpec(a) c = MatchSpec(dst, optional=True, target='burg') d = MatchSpec(a, build='5') assert a == b assert hash(a) == hash(b) assert a is b assert a != c assert hash(a) != hash(c) assert a != d assert hash(a) != hash(d) p = MatchSpec(channel='defaults',name='python',version=VersionSpec('3.5*')) assert p.match(Dist(channel='defaults', dist_name='python-3.5.3-1', name='python', version='3.5.3', build_string='1', build_number=1, base_url=None, platform=None)) assert not p.match(Dist(channel='defaults', dist_name='python-3.6.0-0', name='python', version='3.6.0', build_string='0', build_number=0, base_url=None, platform=None)) assert p.match(Dist(channel='defaults', dist_name='python-3.5.1-0', name='python', version='3.5.1', build_string='0', build_number=0, base_url=None, platform=None)) assert p.match(PackageRecord(name='python', version='3.5.1', build='0', build_number=0, depends=('openssl 1.0.2*', 'readline 6.2*', 'sqlite', 'tk 8.5*', 'xz 5.0.5', 'zlib 1.2*', 'pip'), channel=Channel(scheme='https', auth=None, location='repo.anaconda.com', token=None, name='pkgs/main', platform='osx-64', package_filename=None), subdir='osx-64', fn='python-3.5.1-0.tar.bz2', md5='a813bc0a32691ab3331ac9f37125164c', size=14678857, priority=0, url='https://repo.anaconda.com/pkgs/main/osx-64/python-3.5.1-0.tar.bz2'))
def test_match_1(self): for spec, result in [ ('numpy 1.7*', True), ('numpy 1.7.1', True), ('numpy 1.7', False), ('numpy 1.5*', False), ('numpy >=1.5', True), ('numpy >=1.5,<2', True), ('numpy >=1.8,<1.9', False), ('numpy >1.5,<2,!=1.7.1', False), ('numpy >1.8,<2|==1.7', False),('numpy >1.8,<2|>=1.7.1', True), ('numpy >=1.8|1.7*', True), ('numpy ==1.7', False), ('numpy >=1.5,>1.6', True), ('numpy ==1.7.1', True), ('numpy ==1.7.1.0', True), ('numpy==1.7.1.0.0', True), ('numpy >=1,*.7.*', True), ('numpy *.7.*,>=1', True), ('numpy >=1,*.8.*', False), ('numpy >=2,*.7.*', False), ('numpy 1.6*|1.7*', True), ('numpy 1.6*|1.8*', False), ('numpy 1.6.2|1.7*', True), ('numpy 1.6.2|1.7.1', True), ('numpy 1.6.2|1.7.0', False), ('numpy 1.7.1 py27_0', True), ('numpy 1.7.1 py26_0', False), ('numpy >1.7.1a', True), ('python', False), ]: m = MatchSpec(spec) assert m.match(DPkg('numpy-1.7.1-py27_0.tar.bz2')) == result assert 'name' in m assert m.name == 'python' or 'version' in m # both version numbers conforming to PEP 440 assert not MatchSpec('numpy >=1.0.1').match(DPkg('numpy-1.0.1a-0.tar.bz2')) # both version numbers non-conforming to PEP 440 assert not MatchSpec('numpy >=1.0.1.vc11').match(DPkg('numpy-1.0.1a.vc11-0.tar.bz2')) assert MatchSpec('numpy >=1.0.1*.vc11').match(DPkg('numpy-1.0.1a.vc11-0.tar.bz2')) # one conforming, other non-conforming to PEP 440 assert MatchSpec('numpy <1.0.1').match(DPkg('numpy-1.0.1.vc11-0.tar.bz2')) assert MatchSpec('numpy <1.0.1').match(DPkg('numpy-1.0.1a.vc11-0.tar.bz2')) assert not MatchSpec('numpy >=1.0.1.vc11').match(DPkg('numpy-1.0.1a-0.tar.bz2')) assert MatchSpec('numpy >=1.0.1a').match(DPkg('numpy-1.0.1z-0.tar.bz2')) assert MatchSpec('numpy >=1.0.1a py27*').match(DPkg('numpy-1.0.1z-py27_1.tar.bz2')) assert MatchSpec('blas * openblas_0').match(DPkg('blas-1.0-openblas_0.tar.bz2')) assert MatchSpec('blas')._is_simple() assert not MatchSpec('blas 1.0')._is_simple() assert not MatchSpec('blas 1.0 1')._is_simple() m = MatchSpec('blas 1.0', optional=True) m2 = MatchSpec(m, optional=False) m3 = MatchSpec(m2, target='blas-1.0-0.tar.bz2') m4 = MatchSpec(m3, target=None, optional=True) assert m.spec == m2.spec and m.optional != m2.optional assert m2.spec == m3.spec and m2.optional == m3.optional and m2.target != m3.target assert m == m4 self.assertRaises(ValueError, MatchSpec, (1, 2, 3))
def _find_pkgs_to_rebuild(pkgs_with_dep, newest_version, search_rec): """ Return a dictionay of package names which need to be rebuilt Packages need to be rebuilt if the newest version of the packages do not have a package with dependencies that match the search_rec """ search_dep = search_rec['name'] status = defaultdict() for pkg_info in pkgs_with_dep: name = pkg_info['name'] if parse_version(pkg_info['version']) != newest_version[name]: continue for dep in pkg_info.get('depends', []): if not dep.startswith(search_dep): continue spec = MatchSpec(dep) if spec.match(search_rec): status[name] = (False, None) else: if name not in status: status[name] = (True, dep) return {name: dep for name, (rebuild, dep) in status.items() if rebuild}
def get_variants(env): specs = {} for s in env: spec = CondaBuildSpec(s) specs[spec.name] = spec for n, cb_spec in specs.items(): if cb_spec.is_compiler: # This is a compiler package _, lang = cb_spec.raw.split() compiler = conda_build.jinja_context.compiler(lang, config) cb_spec.final = compiler config_key = f"{lang}_compiler" config_version_key = f"{lang}_compiler_version" if conda_build_config.get(config_key): variants[config_key] = conda_build_config[config_key] if conda_build_config.get(config_version_key): variants[config_version_key] = conda_build_config[ config_version_key ] variant_key = n.replace("-", "_") vlist = None if variant_key in conda_build_config: vlist = conda_build_config[variant_key] elif variant_key in default_variant: vlist = [default_variant[variant_key]] if vlist: # we need to check if v matches the spec if cb_spec.is_simple: variants[variant_key] = vlist elif cb_spec.is_pin: # ignore variants? pass else: # check intersection of MatchSpec and variants ms = MatchSpec(cb_spec.raw) filtered = [] for var in vlist: vsplit = var.split() if len(vsplit) == 1: p = { "name": n, "version": vsplit[0], "build_number": 0, "build": "", } elif len(vsplit) == 2: p = { "name": n, "version": var.split()[0], "build": var.split()[1], "build_number": 0, } else: raise RuntimeError("Check your conda_build_config") if ms.match(p): filtered.append(var) else: console.print( f"Configured variant ignored because of the recipe requirement:\n {cb_spec.raw} : {var}\n" ) if len(filtered): variants[variant_key] = filtered return variants
def test_track_features_match(self): dst = Dist('defaults::foo-1.2.3-4.tar.bz2') a = MatchSpec(features='test') assert text_type(a) == "*[features=test]" assert not a.match(DPkg(dst)) assert not a.match(DPkg(dst, track_features='')) a = MatchSpec(track_features='test') assert a.match(DPkg(dst, track_features='test')) assert not a.match(DPkg(dst, track_features='test2')) assert not a.match(DPkg(dst, track_features='test me')) assert not a.match(DPkg(dst, track_features='you test')) assert not a.match(DPkg(dst, track_features='you test me')) assert a.get_exact_value('track_features') == frozenset(('test', )) b = MatchSpec(track_features='mkl') assert not b.match(DPkg(dst)) assert b.match(DPkg(dst, track_features='mkl')) assert b.match(DPkg(dst, track_features='mkl')) assert not b.match(DPkg(dst, track_features='mkl debug')) assert not b.match(DPkg(dst, track_features='debug')) c = MatchSpec(track_features='nomkl') assert not c.match(DPkg(dst)) assert not c.match(DPkg(dst, track_features='mkl')) assert c.match(DPkg(dst, track_features='nomkl')) assert not c.match(DPkg(dst, track_features='nomkl debug')) # regression test for #6860 d = MatchSpec(track_features='') assert d.get_exact_value('track_features') == frozenset() d = MatchSpec(track_features=' ') assert d.get_exact_value('track_features') == frozenset() d = MatchSpec(track_features=('', '')) assert d.get_exact_value('track_features') == frozenset() d = MatchSpec(track_features=('', '', 'test')) assert d.get_exact_value('track_features') == frozenset(('test', ))
def test_track_features_match(self): dst = Dist('defaults::foo-1.2.3-4.tar.bz2') a = MatchSpec(features='test') assert text_type(a) == "*[features=test]" assert not a.match(DPkg(dst)) assert not a.match(DPkg(dst, track_features='')) a = MatchSpec(track_features='test') assert a.match(DPkg(dst, track_features='test')) assert not a.match(DPkg(dst, track_features='test2')) assert not a.match(DPkg(dst, track_features='test me')) assert not a.match(DPkg(dst, track_features='you test')) assert not a.match(DPkg(dst, track_features='you test me')) assert a.get_exact_value('track_features') == frozenset(('test',)) b = MatchSpec(track_features='mkl') assert not b.match(DPkg(dst)) assert b.match(DPkg(dst, track_features='mkl')) assert b.match(DPkg(dst, track_features='mkl')) assert not b.match(DPkg(dst, track_features='mkl debug')) assert not b.match(DPkg(dst, track_features='debug')) c = MatchSpec(track_features='nomkl') assert not c.match(DPkg(dst)) assert not c.match(DPkg(dst, track_features='mkl')) assert c.match(DPkg(dst, track_features='nomkl')) assert not c.match(DPkg(dst, track_features='nomkl debug')) # regression test for #6860 d = MatchSpec(track_features='') assert d.get_exact_value('track_features') == frozenset() d = MatchSpec(track_features=' ') assert d.get_exact_value('track_features') == frozenset() d = MatchSpec(track_features=('', '')) assert d.get_exact_value('track_features') == frozenset() d = MatchSpec(track_features=('', '', 'test')) assert d.get_exact_value('track_features') == frozenset(('test',))
def install(args, parser, command="install"): """ mamba install, mamba update, and mamba create """ context.validate_configuration() check_non_admin() init_api_context(use_mamba_experimental) newenv = bool(command == "create") isinstall = bool(command == "install") solver_task = api.SOLVER_INSTALL isupdate = bool(command == "update") if isupdate: solver_task = api.SOLVER_UPDATE solver_options.clear() if newenv: ensure_name_or_prefix(args, command) prefix = context.target_prefix if newenv: check_prefix(prefix, json=context.json) if context.force_32bit and prefix == context.root_prefix: raise CondaValueError("cannot use CONDA_FORCE_32BIT=1 in base env") if isupdate and not (args.file or args.packages or context.update_modifier == UpdateModifier.UPDATE_ALL): raise CondaValueError("""no package names supplied # If you want to update to a newer version of Anaconda, type: # # $ conda update --prefix %s anaconda """ % prefix) if not newenv: if isdir(prefix): if on_win: delete_trash(prefix) if not isfile(join(prefix, "conda-meta", "history")): if paths_equal(prefix, context.conda_prefix): raise NoBaseEnvironmentError() else: if not path_is_clean(prefix): raise DirectoryNotACondaEnvironmentError(prefix) else: # fall-through expected under normal operation pass else: if hasattr(args, "mkdir") and args.mkdir: try: mkdir_p(prefix) except EnvironmentError as e: raise CondaOSError("Could not create directory: %s" % prefix, caused_by=e) else: raise EnvironmentLocationNotFound(prefix) prefix = context.target_prefix ############################# # Get SPECS # ############################# args_packages = [s.strip("\"'") for s in args.packages] if newenv and not args.no_default_packages: # Override defaults if they are specified at the command line # TODO: rework in 4.4 branch using MatchSpec args_packages_names = [ pkg.replace(" ", "=").split("=", 1)[0] for pkg in args_packages ] for default_pkg in context.create_default_packages: default_pkg_name = default_pkg.replace(" ", "=").split("=", 1)[0] if default_pkg_name not in args_packages_names: args_packages.append(default_pkg) num_cp = sum(s.endswith(".tar.bz2") for s in args_packages) if num_cp: if num_cp == len(args_packages): explicit(args_packages, prefix, verbose=not (context.quiet or context.json)) return else: raise CondaValueError( "cannot mix specifications with conda package" " filenames") specs = [] index_args = { "use_cache": args.use_index_cache, "channel_urls": context.channels, "unknown": args.unknown, "prepend": not args.override_channels, "use_local": args.use_local, } if args.file: file_specs = [] for fpath in args.file: try: file_specs += specs_from_url(fpath, json=context.json) except UnicodeError: raise CondaValueError( "Error reading file, file should be a text file containing" " packages \nconda create --help for details") if "@EXPLICIT" in file_specs: explicit( file_specs, prefix, verbose=not (context.quiet or context.json), index_args=index_args, ) return specs.extend([MatchSpec(s) for s in file_specs]) specs.extend(specs_from_args(args_packages, json=context.json)) # update channels from package specs (e.g. mychannel::mypackage adds mychannel) channels = [c for c in context.channels] for spec in specs: # CONDA TODO: correct handling for subdir isn't yet done spec_channel = spec.get_exact_value("channel") if spec_channel and spec_channel not in channels: channels.append(spec_channel) index_args["channel_urls"] = channels installed_json_f, installed_pkg_recs = get_installed_jsonfile(prefix) if isinstall and args.revision: get_revision(args.revision, json=context.json) elif isinstall and not (args.file or args_packages): raise CondaValueError( "too few arguments, " "must supply command line package specs or --file") installed_names = [i_rec.name for i_rec in installed_pkg_recs] # for 'conda update', make sure the requested specs actually exist in the prefix # and that they are name-only specs if isupdate and context.update_modifier == UpdateModifier.UPDATE_ALL: for i in installed_names: if i != "python": specs.append(MatchSpec(i)) prefix_data = PrefixData(prefix) for s in args_packages: s = MatchSpec(s) if s.name == "python": specs.append(s) if not s.is_name_only_spec: raise CondaValueError("Invalid spec for 'conda update': %s\n" "Use 'conda install' instead." % s) if not prefix_data.get(s.name, None): raise PackageNotInstalledError(prefix, s.name) elif context.update_modifier == UpdateModifier.UPDATE_DEPS: # find the deps for each package and add to the update job # solver_task |= api.SOLVER_FORCEBEST final_specs = specs for spec in specs: prec = installed_pkg_recs[installed_names.index(spec.name)] for dep in prec.depends: ms = MatchSpec(dep) if ms.name != "python": final_specs.append(MatchSpec(ms.name)) specs = set(final_specs) if newenv and args.clone: if args.packages: raise TooManyArgumentsError( 0, len(args.packages), list(args.packages), "did not expect any arguments for --clone", ) clone( args.clone, prefix, json=context.json, quiet=(context.quiet or context.json), index_args=index_args, ) touch_nonadmin(prefix) print_activate(args.name if args.name else prefix) return if not (context.quiet or context.json): print("\nLooking for: {}\n".format([str(s) for s in specs])) spec_names = [s.name for s in specs] # If python was not specified, check if it is installed. # If yes, add the installed python to the specs to prevent updating it. python_constraint = None if "python" not in spec_names: if "python" in installed_names: i = installed_names.index("python") version = installed_pkg_recs[i].version python_constraint = MatchSpec("python==" + version).conda_build_form() mamba_solve_specs = [s.__str__() for s in specs] if context.channel_priority is ChannelPriority.STRICT: solver_options.append((api.SOLVER_FLAG_STRICT_REPO_PRIORITY, 1)) pool = api.Pool() repos = [] prefix_data = api.PrefixData(context.target_prefix) prefix_data.load() # add installed if use_mamba_experimental: repo = api.Repo(pool, prefix_data) repos.append(repo) else: repo = api.Repo(pool, "installed", installed_json_f.name, "") repo.set_installed() repos.append(repo) if newenv and not specs: # creating an empty environment with e.g. "mamba create -n my_env" # should not download the repodata index = [] specs_to_add = [] specs_to_remove = [] to_link = [] to_unlink = [] installed_pkg_recs = [] else: index = load_channels(pool, channels, repos) if context.force_reinstall: solver = api.Solver(pool, solver_options, prefix_data) else: solver = api.Solver(pool, solver_options) solver.set_postsolve_flags([ (api.MAMBA_NO_DEPS, context.deps_modifier == DepsModifier.NO_DEPS), (api.MAMBA_ONLY_DEPS, context.deps_modifier == DepsModifier.ONLY_DEPS), (api.MAMBA_FORCE_REINSTALL, context.force_reinstall), ]) if context.update_modifier is UpdateModifier.FREEZE_INSTALLED: solver.add_jobs([p for p in prefix_data.package_records], api.SOLVER_LOCK) solver.add_jobs(mamba_solve_specs, solver_task) if not context.force_reinstall: # as a security feature this will _always_ attempt to upgradecertain # packages for a_pkg in [_.name for _ in context.aggressive_update_packages]: if a_pkg in installed_names: solver.add_jobs([a_pkg], api.SOLVER_UPDATE) pinned_specs_info = "" if python_constraint: solver.add_pin(python_constraint) pinned_specs_info += f" - {python_constraint}\n" pinned_specs = get_pinned_specs(context.target_prefix) if pinned_specs: conda_prefix_data = PrefixData(context.target_prefix) for s in pinned_specs: x = conda_prefix_data.query(s.name) if x: for el in x: if not s.match(el): print( "Your pinning does not match what's currently installed." " Please remove the pin and fix your installation") print(" Pin: {}".format(s)) print(" Currently installed: {}".format(el)) exit(1) try: final_spec = s.conda_build_form() pinned_specs_info += f" - {final_spec}\n" solver.add_pin(final_spec) except AssertionError: print(f"\nERROR: could not add pinned spec {s}. Make sure pin" "is of the format\n" "libname VERSION BUILD, for example libblas=*=*mkl\n") if pinned_specs_info and not (context.quiet or context.json): print(f"\nPinned packages:\n{pinned_specs_info}\n") success = solver.solve() if not success: print(solver.problems_to_str()) exit_code = 1 return exit_code package_cache = api.MultiPackageCache(context.pkgs_dirs) transaction = api.Transaction( solver, package_cache, PackageCacheData.first_writable().pkgs_dir) mmb_specs, to_link, to_unlink = transaction.to_conda() specs_to_add = [MatchSpec(m) for m in mmb_specs[0]] specs_to_remove = [MatchSpec(m) for m in mmb_specs[1]] transaction.log_json() downloaded = transaction.prompt(repos) if not downloaded: exit(0) PackageCacheData.first_writable().reload() # if use_mamba_experimental and not os.name == "nt": if use_mamba_experimental: if newenv and not isdir(context.target_prefix) and not context.dry_run: mkdir_p(prefix) transaction.execute(prefix_data) else: conda_transaction = to_txn( specs_to_add, specs_to_remove, prefix, to_link, to_unlink, installed_pkg_recs, index, ) handle_txn(conda_transaction, prefix, args, newenv) try: installed_json_f.close() os.unlink(installed_json_f.name) except Exception: pass
def test_key_value_features_match(self): dst = Dist('defaults::foo-1.2.3-4.tar.bz2') a = MatchSpec(features='test') assert text_type(a) == "*[provides_features='test=true']" assert not a.match(DPkg(dst)) assert not a.match(DPkg(dst, track_features='')) assert a.match(DPkg(dst, track_features='test')) assert not a.match(DPkg(dst, track_features='test2')) assert a.match(DPkg(dst, track_features='test me')) assert a.match(DPkg(dst, track_features='you test')) assert a.match(DPkg(dst, track_features='you test me')) assert a.get_exact_value('provides_features') == frozendict({'test': 'true'}) b = MatchSpec(features='mkl') assert not b.match(DPkg(dst)) assert b.match(DPkg(dst, track_features='mkl')) assert b.match(DPkg(dst, track_features='blas=mkl')) assert b.match(DPkg(dst, track_features='blas=mkl debug')) assert not b.match(DPkg(dst, track_features='debug')) c = MatchSpec(features='nomkl') assert not c.match(DPkg(dst)) assert not c.match(DPkg(dst, track_features='mkl')) assert c.match(DPkg(dst, track_features='nomkl')) assert c.match(DPkg(dst, track_features='blas=nomkl debug'))