#!/usr/bin/env python3 # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. """Tests for gen_requirements, found in python/.""" import collections import contextlib import os import sys import tvm import pytest # Insert the parent dir to python/tvm into the import path, so that gen_requirements may be # imported. sys.path.insert(0, os.path.dirname(tvm.__file__)) try: import gen_requirements finally: sys.path.pop(0) @contextlib.contextmanager def patch(obj, **kw): old = {} for prop_name, new in kw.items(): old[prop_name] = getattr(obj, prop_name) setattr(obj, prop_name, new) yield for prop_name, value in old.items(): setattr(obj, prop_name, value) PROBLEM_REQUIREMENTS = [ ("extras-pre-core", ("", ["foo", 123])), # entry before core (456, ("", ["foo", "bar"])), # invalid extras name, deps should not be processed ("core", ("", ["foo"])), # ordinary core entry. ("wrong-description-type", (None, ["foo"])), # wrong description type ("bad-value", None), # value field is not a 2-tuple ("bad-value-2", ("", ["foo"], 34)), # value field is not a 2-tuple ("invalid", ("", ["qux"])), # duplicate invalid entry, all items valid. ("extras-foo", ("", ["bar", "baz"])), # ordinary extras entry. ("invalid", ("", ["baz", None, 123])), # valid extra name, invalid deps. ("unsorted", ("", ["qux", "bar", "foo"])), # deps out of order ("versioned_dep", ("", ["baz==1.2", "foo==^2.0", "buz<3", "bar>4"])), ("duplicate_dep", ("", ["buz", "buz", "foo"])), # duplicate listed dependency ("dev", ("", ["baz", "qux"])), # ordinary dev entry. ("extras-post-dev", ("", ["bar", "buzz"])), # entry after dev ] def test_validate_requirements(): with patch(gen_requirements, REQUIREMENTS_BY_PIECE=None): assert gen_requirements.validate_requirements_by_piece() == [ "must be list or tuple, see None" ] with patch(gen_requirements, REQUIREMENTS_BY_PIECE=PROBLEM_REQUIREMENTS): problems = gen_requirements.validate_requirements_by_piece() assert problems == [ 'piece extras-pre-core: must list after "core" (core must be first)', "piece extras-pre-core: deps should be a list of strings, got ['foo', 123]", "piece 456: must be str", "piece wrong-description-type: description should be a string, got None", ( 'piece bad-value: should be formatted like ("bad-value", ("", ["dep1", "dep2", ...])). got: None' ), ( 'piece bad-value-2: should be formatted like ("bad-value-2", ' '("", ["dep1", "dep2", ...])). got: (\'\', ' "['foo'], 34)" ), "piece invalid: listed twice", "piece invalid: deps should be a list of strings, got ['baz', None, 123]", "piece unsorted: deps must be sorted. Correct order:\n ['bar', 'foo', 'qux']", "piece versioned_dep: deps must be sorted. Correct order:\n ['bar>4', 'baz==1.2', 'buz<3', 'foo==^2.0']", "piece versioned_dep: dependency baz==1.2 should not specify a version. Add it to CONSTRAINTS instead.", "piece versioned_dep: dependency foo==^2.0 should not specify a version. Add it to CONSTRAINTS instead.", "piece versioned_dep: dependency buz<3 should not specify a version. Add it to CONSTRAINTS instead.", "piece versioned_dep: dependency bar>4 should not specify a version. Add it to CONSTRAINTS instead.", "piece duplicate_dep: dependency buz listed twice", 'piece extras-post-dev: must list before "dev" (dev must be last)', 'pieces other than "core" and "dev" must appear in alphabetical order: ' "['bad-value', 'bad-value-2', 'duplicate_dep', 'extras-foo', 'extras-post-dev', " "'extras-pre-core', 'invalid', 'invalid', 'unsorted', 'versioned_dep', " "'wrong-description-type']", ] TEST_REQUIREMENTS_BY_PIECE = ( ("core", ("core tvm requirements", ("bar", "foo", "non-constrained"))), ("extra-one", ("requirements for one feature", ("baz", "qux"))), ("extra-two", ("requirements for two feature", ("buz", "qux", "semver-minor", "semver-patch"))), ("dev", ("requirements for dev", ("buz", "oof", "rab"))), ) def test_validate_constraints(): with patch( gen_requirements, REQUIREMENTS_BY_PIECE=TEST_REQUIREMENTS_BY_PIECE, CONSTRAINTS=( ("unlisted", "~=3"), ("double-specified", "<2"), ( "double-specified", "==3", ), ("bad-constraint", "1.2.0"), ("bad-semver-constraint", "i don't match the regex :P"), ("alpha-semver-constraint", "^foo.bar.23"), ), ): problems = gen_requirements.validate_constraints() assert problems == [ "unlisted: not specified in REQUIREMENTS_BY_PIECE", "double-specified: not specified in REQUIREMENTS_BY_PIECE", "double-specified: specified twice", "double-specified: not specified in REQUIREMENTS_BY_PIECE", "bad-constraint: not specified in REQUIREMENTS_BY_PIECE", 'bad-constraint: constraint "1.2.0" does not look like a valid constraint', "bad-semver-constraint: not specified in REQUIREMENTS_BY_PIECE", 'bad-semver-constraint: constraint "i don\'t match the regex :P" does not look like a valid constraint', "alpha-semver-constraint: not specified in REQUIREMENTS_BY_PIECE", "alpha-semver-constraint: invalid semver constraint ^foo.bar.23", "CONSTRAINTS entries should be in this sorted order: ['alpha-semver-constraint', 'bad-constraint', 'bad-semver-constraint', 'double-specified', 'double-specified', 'unlisted']", ] TEST_CONSTRAINTS = ( ("bar", "==1.0"), ("baz", ">2.3"), ("buz", "^1.3.0"), ("non-constrained", None), # Support a comment. ("oof", "==0.3.4"), ("qux", "~=1.2.4"), ("semver-minor", "^0.2.2-patch2.post3+buildmeta"), # Ensure prerelease and buildmeta preserved. ("semver-patch", "^0.0.2+bm"), # Ensure postrelease preserved. ) def test_join_requirements(): with patch( gen_requirements, REQUIREMENTS_BY_PIECE=TEST_REQUIREMENTS_BY_PIECE, CONSTRAINTS=TEST_CONSTRAINTS, ): requirements = gen_requirements.join_requirements() assert requirements == collections.OrderedDict( [ ("core", ("core tvm requirements", ["bar==1.0", "foo", "non-constrained"])), ("extra-one", ("requirements for one feature", ["baz>2.3", "qux~=1.2.4"])), ( "extra-two", ( "requirements for two feature", [ "buz>=1.3.0,<2.0.0", "qux~=1.2.4", "semver-minor>=0.2.2-patch2.post3+buildmeta,<0.3.0", "semver-patch>=0.0.2+bm,<0.0.3", ], ), ), ("dev", ("requirements for dev", ["buz>=1.3.0,<2.0.0", "oof==0.3.4", "rab"])), ( "all-prod", ( "Combined dependencies for all TVM pieces, excluding dev", [ "bar==1.0", "baz>2.3", "buz>=1.3.0,<2.0.0", "foo", "non-constrained", "qux~=1.2.4", "semver-minor>=0.2.2-patch2.post3+buildmeta,<0.3.0", "semver-patch>=0.0.2+bm,<0.0.3", ], ), ), ] ) def test_semver(): problems = [] assert gen_requirements.parse_semver("C", "^1.2.0", problems) == (["1", "2", "0"], 0, 1) assert problems == [] assert gen_requirements.parse_semver("C", "^0.2.0", problems) == (["0", "2", "0"], 1, 2) assert problems == [] assert gen_requirements.parse_semver("C", "^0.0.0", problems) == (["0", "0", "0"], 0, 0) assert problems == [] assert gen_requirements.parse_semver("C", "^0.a.0", problems) == ([], 0, 0) assert problems == ["C: invalid semver constraint ^0.a.0"] if __name__ == "__main__": sys.exit(pytest.main([__file__] + sys.argv[1:]))