Exemplo n.º 1
0
 def _inv(a, b):
     a_range = VersionRange(a)
     b_range = VersionRange(b)
     self.assertTrue(~a_range == b_range)
     self.assertTrue(~b_range == a_range)
     self.assertTrue(a_range | b_range == VersionRange())
     self.assertTrue(a_range & b_range is None)
Exemplo n.º 2
0
def intersects(obj, range_):
    """Test if an object intersects with the given version range.

    Examples:

        # in package.py
        def commands():
            # test a request
            if intersects(request.maya, '2019+'):
                info('requested maya allows >=2019.*')

            # tests if a resolved version intersects with given range
            if intersects(resolve.maya, '2019+')
                ...

            # same as above
            if intersects(resolve.maya.version, '2019+')
                ...

        # disable my cli tools if .foo.cli-0 was specified
        def commands():
            if intersects(ephemerals.get('foo.cli', '1'), '1'):
                env.PATH.append('{root}/bin')

    Args:
        obj (VariantBinding or str): Object to test, either a
            variant, or requirement string (eg 'foo-1.2.3+').
        range_ (str): Version range, eg '1.2+<2'

    Returns:
        bool: True if the object intersects the given range.
    """
    range1 = VersionRange(range_)

    # eg 'if intersects(request.maya, ...)'
    if isinstance(obj, basestring):
        req = Requirement(obj)
        if req.conflict:
            return False
        range2 = req.range

    # eg 'if intersects(ephemerals.get_range('foo.cli', '1'), ...)'
    elif isinstance(obj, VersionRange):
        range2 = obj

    # eg 'if intersects(resolve.maya, ...)'
    elif isinstance(obj, VariantBinding):
        range2 = VersionRange(str(obj.version))

    # eg 'if intersects(resolve.maya.version, ...)'
    elif isinstance(obj, VersionBinding):
        range2 = VersionRange(str(obj))

    else:
        raise RuntimeError(
            "Invalid type %s passed as first arg to 'intersects'" % type(obj)
        )

    return range1.intersects(range2)
Exemplo n.º 3
0
        def _and(a, b, c):
            _print("'%s' & '%s' == '%s'" % (a, b, c))
            a_range = VersionRange(a)
            b_range = VersionRange(b)
            c_range = None if c is None else VersionRange(c)
            self.assertTrue(a_range & b_range == c_range)
            self.assertTrue(b_range & a_range == c_range)

            a_or_b = a_range | b_range
            a_and_b = a_range & b_range
            a_sub_b = a_range - b_range
            b_sub_a = b_range - a_range
            ranges = [a_and_b, a_sub_b, b_sub_a]
            ranges = [x for x in ranges if x]
            self.assertTrue(ranges[0].union(ranges[1:]) == a_or_b)
Exemplo n.º 4
0
 def _get_dest_pkg(self, name, version):
     return get_latest_package(
         name,
         range_=VersionRange("==" + version),
         paths=[self.dest_install_root],
         error=True
     )
Exemplo n.º 5
0
    def expand_version(version):
        rank = len(version)
        wildcard_found = False

        while version and str(version[-1]) in wildcard_map:
            token = wildcard_map[str(version[-1])]
            version = version.trim(len(version) - 1)

            if token == "**":
                if wildcard_found:  # catches bad syntax '**.*'
                    return None
                else:
                    wildcard_found = True
                    rank = 0
                    break

            wildcard_found = True

        if not wildcard_found:
            return None

        range_ = VersionRange(str(version))
        package = get_latest_package(name=req.name, range_=range_, paths=paths)

        if package is None:
            return version

        if rank:
            return package.version.trim(rank)
        else:
            return package.version
Exemplo n.º 6
0
def iter_packages(name, range_=None, paths=None):
    """Iterate over `Package` instances, in no particular order.

    Packages of the same name and version earlier in the search path take
    precedence - equivalent packages later in the paths are ignored. Packages
    are not returned in any specific order.

    Args:
        name (str): Name of the package, eg 'maya'.
        range_ (VersionRange or str): If provided, limits the versions returned
            to those in `range_`.
        paths (list of str, optional): paths to search for packages, defaults
            to `config.packages_path`.

    Returns:
        `Package` iterator.
    """
    entries = _get_families(name, paths)

    seen = set()
    for repo, family_resource in entries:
        for package_resource in repo.iter_packages(family_resource):
            key = (package_resource.name, package_resource.version)
            if key in seen:
                continue

            seen.add(key)
            if range_:
                if isinstance(range_, basestring):
                    range_ = VersionRange(range_)
                if package_resource.version not in range_:
                    continue

            yield Package(package_resource)
Exemplo n.º 7
0
 def get_range(self, name, default=None):
     """Returns ephemeral version range object"""
     req_str = self._data.get(name)
     if req_str:
         return Requirement(req_str).range
     elif default is not None:
         return VersionRange(default)
     else:
         return None
Exemplo n.º 8
0
    def construct(cls, name, range=None):
        """Create a requirement directly from an object name and VersionRange.

        Args:
            name: Object name string.
            range: VersionRange object. If None, an unversioned requirement is
                created.
        """
        other = Requirement(None)
        other.name_ = name
        other.range_ = VersionRange() if range is None else range
        return other
Exemplo n.º 9
0
    def __init__(self, s, invalid_bound_error=True):
        self.name_ = None
        self.range_ = None
        self.negate_ = False
        self.conflict_ = False
        self._str = None
        self.sep_ = '-'
        if s is None:
            return

        self.conflict_ = s.startswith('!')
        if self.conflict_:
            s = s[1:]
        elif s.startswith('~'):
            s = s[1:]
            self.negate_ = True
            self.conflict_ = True

        m = self.sep_regex.search(s)
        if m:
            i = m.start()
            self.name_ = s[:i]
            req_str = s[i:]
            if req_str[0] in ('-', '@', '#'):
                self.sep_ = req_str[0]
                req_str = req_str[1:]

            self.range_ = VersionRange(req_str,
                                       invalid_bound_error=invalid_bound_error)
            if self.negate_:
                self.range_ = ~self.range_
        elif self.negate_:
            self.name_ = s
            # rare case - '~foo' equates to no effect
            self.range_ = None
        else:
            self.name_ = s
            self.range_ = VersionRange()
Exemplo n.º 10
0
    def __str__(self):
        pre_str = '~' if self.negate_ else ('!' if self.conflict_ else '')
        range_str = ''
        sep_str = ''

        range = self.range_
        if self.negate_:
            range = ~range if range else VersionRange()

        if not range.is_any():
            range_str = str(range)
            if range_str[0] not in ('=', '<', '>'):
                sep_str = self.sep_

        return pre_str + self.name_ + sep_str + range_str
Exemplo n.º 11
0
    def __str__(self):
        if self._str is None:
            pre_str = '~' if self.negate_ else ('!' if self.conflict_ else '')
            range_str = ''
            sep_str = ''

            range_ = self.range_
            if self.negate_:
                range_ = ~range_ if range_ else VersionRange()

            if not range_.is_any():
                range_str = str(range_)
                if range_str[0] not in ('=', '<', '>'):
                    sep_str = self.sep_

            self._str = pre_str + self.name_ + sep_str + range_str
        return self._str
Exemplo n.º 12
0
        def _eq(a, b):
            _print("'%s' == '%s'" % (a, b))
            a_range = VersionRange(a)
            b_range = VersionRange(b)

            self.assertTrue(a_range == b_range)
            self.assertTrue(a_range.issuperset(a_range))
            self.assertTrue(a_range.issuperset(b_range))
            self.assertTrue(VersionRange(str(a_range)) == a_range)
            self.assertTrue(VersionRange(str(b_range)) == a_range)
            self.assertTrue(hash(a_range) == hash(b_range))

            a_ = a.replace('.', '-')
            a_ = a_.replace("--", "..")
            a_range_ = VersionRange(a_)
            self.assertTrue(a_range_ == a_range)
            self.assertTrue(hash(a_range_) == hash(a_range))

            range_strs = a.split('|')
            ranges = [VersionRange(x) for x in range_strs]
            ranges_ = ranges[0].union(ranges[1:])
            self.assertTrue(ranges_ == a_range)

            self.assertTrue(a_range | b_range == a_range)
            self.assertTrue(a_range - b_range is None)
            self.assertTrue(b_range - a_range is None)
            self.assertTrue(VersionRange() & a_range == a_range)
            self.assertTrue(b_range.span() & a_range == a_range)

            a_inv = a_range.inverse()
            self.assertTrue(a_inv == ~b_range)

            if a_inv:
                self.assertTrue(~a_inv == a_range)
                self.assertTrue(a_range | a_inv == VersionRange())
                self.assertTrue(a_range & a_inv is None)

            a_ranges = a_range.split()
            a_range_ = a_ranges[0].union(a_ranges[1:])
            self.assertTrue(a_range_ == b_range)
Exemplo n.º 13
0
def get_package(name, version, paths=None):
    """Get an exact version of a package.

    Args:
        name (str): Name of the package, eg 'maya'.
        version (Version or str): Version of the package, eg '1.0.0'
        paths (list of str, optional): paths to search for package, defaults
            to `config.packages_path`.

    Returns:
        `Package` object, or None if the package was not found.
    """
    if isinstance(version, basestring):
        range_ = VersionRange("==%s" % version)
    else:
        range_ = VersionRange.from_version(version, "==")

    it = iter_packages(name, range_, paths)
    try:
        return it.next()
    except StopIteration:
        return None
Exemplo n.º 14
0
 def assertPipRezEquivalent(pip_spec_str, rez_req_str):
     pip_spec = SpecifierSet(pip_spec_str)
     self.assertEqual(
         rez.utils.pip.pip_specifier_to_rez_requirement(pip_spec),
         VersionRange(rez_req_str))
Exemplo n.º 15
0
    def test_version_range(self):
        def _eq(a, b):
            _print("'%s' == '%s'" % (a, b))
            a_range = VersionRange(a)
            b_range = VersionRange(b)

            self.assertTrue(a_range == b_range)
            self.assertTrue(a_range.issuperset(a_range))
            self.assertTrue(a_range.issuperset(b_range))
            self.assertTrue(VersionRange(str(a_range)) == a_range)
            self.assertTrue(VersionRange(str(b_range)) == a_range)
            self.assertTrue(hash(a_range) == hash(b_range))

            a_ = a.replace('.', '-')
            a_ = a_.replace("--", "..")
            a_range_ = VersionRange(a_)
            self.assertTrue(a_range_ == a_range)
            self.assertTrue(hash(a_range_) == hash(a_range))

            range_strs = a.split('|')
            ranges = [VersionRange(x) for x in range_strs]
            ranges_ = ranges[0].union(ranges[1:])
            self.assertTrue(ranges_ == a_range)

            self.assertTrue(a_range | b_range == a_range)
            self.assertTrue(a_range - b_range is None)
            self.assertTrue(b_range - a_range is None)
            self.assertTrue(VersionRange() & a_range == a_range)
            self.assertTrue(b_range.span() & a_range == a_range)

            a_inv = a_range.inverse()
            self.assertTrue(a_inv == ~b_range)

            if a_inv:
                self.assertTrue(~a_inv == a_range)
                self.assertTrue(a_range | a_inv == VersionRange())
                self.assertTrue(a_range & a_inv is None)

            a_ranges = a_range.split()
            a_range_ = a_ranges[0].union(a_ranges[1:])
            self.assertTrue(a_range_ == b_range)

        def _and(a, b, c):
            _print("'%s' & '%s' == '%s'" % (a, b, c))
            a_range = VersionRange(a)
            b_range = VersionRange(b)
            c_range = None if c is None else VersionRange(c)
            self.assertTrue(a_range & b_range == c_range)
            self.assertTrue(b_range & a_range == c_range)

            a_or_b = a_range | b_range
            a_and_b = a_range & b_range
            a_sub_b = a_range - b_range
            b_sub_a = b_range - a_range
            ranges = [a_and_b, a_sub_b, b_sub_a]
            ranges = [x for x in ranges if x]
            self.assertTrue(ranges[0].union(ranges[1:]) == a_or_b)

        def _inv(a, b):
            a_range = VersionRange(a)
            b_range = VersionRange(b)
            self.assertTrue(~a_range == b_range)
            self.assertTrue(~b_range == a_range)
            self.assertTrue(a_range | b_range == VersionRange())
            self.assertTrue(a_range & b_range is None)

        # simple cases
        _print()
        _eq("", "")
        _eq("1", "1")
        _eq("1.0.0", "1.0.0")
        _eq("3+<3_", "3")
        _eq("_+<__", "_")
        _eq("1.2+<=2.0", "1.2..2.0")
        _eq("10+,<20", "10+<20")
        _eq("1+<1.0", "1+<1.0")
        _eq(">=2", "2+")

        # optimised cases
        _eq("3|3", "3")
        _eq("3|1", "1|3")
        _eq("5|3|1", "1|3|5")
        _eq("1|1_", "1+<1__")
        _eq("1|1_|1__", "1+,<1___")
        _eq("|", "")
        _eq("||", "||||||||")
        _eq("1|1_+", "1+")
        _eq("<1|1", "<1_")
        _eq("1+<3|3+<5", "1+<5")
        _eq(">4<6|1+<3", "1+<3|>4,<6")
        _eq("4+<6|1+<3|", "")
        _eq("4|2+", "2+")
        _eq("3|<5", "<5")
        _eq("<3|>3", ">3|<3")
        _eq("3+|<3", "")
        _eq("3+|<4", "")
        _eq("2+<=6|3+<5", "2..6")
        _eq("3+,<5|2+<=6", "2+<=6")
        _eq("2|2+", "2+")
        _eq("2|2.1+", "2+")
        _eq("2|<2.1", "<2_")
        _eq("3..3", "==3")
        _eq(">=3,<=3", "==3")

        # AND'ing
        _and("3", "3", "3")
        _and("1", "==1", "==1")
        _and("", "==1", "==1")
        _and("3", "4", None)
        _and("<3", "5+", None)
        _and("4+<6", "6+<8", None)
        _and("2+", "<=4", "2..4")
        _and("1", "1.0", "1.0")
        _and("4..6", "6+<8", "==6")

        # inverse
        _inv("3+", "<3")
        _inv("<=3", ">3")
        _inv("3.5", "<3.5|3.5_+")
        self.assertTrue(~VersionRange() is None)

        # odd (but valid) cases
        _eq(">", ">")  # greater than the empty version
        _eq("+", "")  # greater or equal to empty version (is all vers)
        _eq(">=", "")  # equivalent to above
        _eq("<=", "==")  # less or equal to empty version (is only empty)
        _eq("..", "==")  # from empty version to empty version
        _eq("+<=", "==")  # equivalent to above

        invalid_range = [
            "4+<2",  # lower bound greater than upper
            ">3<3",  # both greater and less than same version
            ">3<=3",  # greater and less or equal to same version
            "3+<3"  # greater and equal to, and less than, same version
        ]

        for s in invalid_range:
            self.assertRaises(VersionError, VersionRange, s)

        invalid_syntax = [
            "<",  # less than the empty version
            "><",  # both greater and less than empty version
            ">3>4",  # both are lower bounds
            "<3<4",  # both are upper bounds
            "<4>3",  # upper bound before lower
            ",<4",  # leading comma
            "4+,",  # trailing comma
            "1>=",  # pre-lower-op in post
            "+1",  # post-lower-op in pre
            "4<",  # pre-upper-op in post
            "1+<2<3"  # more than two bounds
        ]

        for s in invalid_syntax:
            self.assertRaises(VersionError, VersionRange, s)

        # test simple logic
        self.assertTrue(VersionRange("").is_any())
        self.assertTrue(VersionRange("2+<4").bounded())
        self.assertTrue(VersionRange("2+").lower_bounded())
        self.assertTrue(not VersionRange("2+").upper_bounded())
        self.assertTrue(not VersionRange("2+").bounded())
        self.assertTrue(VersionRange("<2").upper_bounded())
        self.assertTrue(not VersionRange("<2").lower_bounded())
        self.assertTrue(not VersionRange("<2").bounded())

        # test range from version(s)
        v = Version("3")
        self.assertTrue(
            VersionRange.from_version(v, "eq") == VersionRange("==3"))
        self.assertTrue(
            VersionRange.from_version(v, "gt") == VersionRange(">3"))
        self.assertTrue(
            VersionRange.from_version(v, "gte") == VersionRange("3+"))
        self.assertTrue(
            VersionRange.from_version(v, "lt") == VersionRange("<3"))
        self.assertTrue(
            VersionRange.from_version(v, "lte") == VersionRange("<=3"))

        range1 = VersionRange.from_version(Version("2"), "gte")
        range2 = VersionRange.from_version(Version("4"), "lte")
        _eq(str(range1 & range2), "2..4")

        v2 = Version("6.0")
        v3 = Version("4")
        self.assertTrue(
            VersionRange.from_versions([v, v2, v3]) == VersionRange(
                "==3|==4|==6.0"))

        # test behaviour in sets
        def _eq2(a, b):
            _print("'%s' == '%s'" % (a, b))
            self.assertTrue(a == b)

        a = VersionRange("1+<=2.5")
        b = VersionRange("1..2.5")
        c = VersionRange(">=5")
        d = VersionRange(">6.1.0")
        e = VersionRange("3.2")

        _eq2(set([a]) - set([a]), set())
        _eq2(set([a]) - set([b]), set())
        _eq2(set([a, a]) - set([a]), set())
        _eq2(set([b, c, d, e]) - set([a]), set([c, d, e]))
        _eq2(set([b, c, e]) | set([c, d]), set([b, c, d, e]))
        _eq2(set([b, c]) & set([c, d]), set([c]))
Exemplo n.º 16
0
def pip_specifier_to_rez_requirement(specifier):
    """Convert PEP440 version specifier to rez equivalent.

    See https://www.python.org/dev/peps/pep-0440/#version-specifiers

    Note that version numbers in the specifier are converted to rez equivalents
    at the same time. Thus a specifier like '<1.ALPHA2' becomes '<1.a2'.

    Note that the conversion is not necessarily exact - there are cases in
    PEP440 that have no equivalent in rez versioning. Most of these are
    specifiers that involve pre/post releases, which don't exist in rez (or
    rather, they do exist in the sense that '1.0.post1' is a valid rez version
    number, but it has no special meaning).

    Note also that the specifier is being converted into rez format, but in a
    way that still expresses how _pip_ interprets the specifier. For example,
    '==1' is a valid version range in rez, but '==1' has a different meaning to
    pip than it does to rez ('1.0' matches '==1' in pip, but not in rez). This
    is why '==1' is converted to '1+<1.1' in rez, rather than '==1'.

    Example conversions:

        |   PEP440    |     rez     |
        |-------------|-------------|
        | ==1         | 1+<1.1      |
        | ==1.*       | 1           |
        | >1          | 1.1+        |
        | <1          | <1          |
        | >=1         | 1+          |
        | <=1         | <1.1        |
        | ~=1.2       | 1.2+<2      |
        | ~=1.2.3     | 1.2.3+<1.3  |
        | !=1         | <1|1.1+     |
        | !=1.2       | <1.2|1.2.1+ |
        | !=1.*       | <1|2+       |
        | !=1.2.*     | <1.2|1.3+   |

    Args:
        specifier (`package.SpecifierSet`): Pip specifier.

    Returns:
        `VersionRange`: Equivalent rez version range.
    """
    def is_release(rez_ver):
        parts = rez_ver.split('.')
        try:
            _ = int(parts[-1])  # noqa
            return True
        except:
            return False

    # 1 --> 2; 1.2 --> 1.3; 1.a2 -> 1.0
    def next_ver(rez_ver):
        parts = rez_ver.split('.')
        if is_release(rez_ver):
            parts = parts[:-1] + [str(int(parts[-1]) + 1)]
        else:
            parts = parts[:-1] + ["0"]
        return '.'.join(parts)

    # 1 --> 1.1; 1.2 --> 1.2.1; 1.a2 --> 1.0
    def adjacent_ver(rez_ver):
        if is_release(rez_ver):
            return rez_ver + ".1"
        else:
            parts = rez_ver.split('.')
            parts = parts[:-1] + ["0"]
            return '.'.join(parts)

    def convert_spec(spec):
        def parsed_rez_ver():
            v = spec.version.replace(".*", "")
            return pip_to_rez_version(v)

        def fmt(txt):
            v = parsed_rez_ver()
            vnext = next_ver(v)
            vadj = adjacent_ver(v)
            return txt.format(V=v, VNEXT=vnext, VADJ=vadj)

        # ==1.* --> 1
        if spec.operator == "==" and spec.version.endswith(".*"):
            return fmt("{V}")

        # ==1 --> 1+<1.1
        if spec.operator == "==":
            return fmt("{V}+<{VADJ}")

        # >=1 --> 1+
        if spec.operator == ">=":
            return fmt("{V}+")

        # >1 --> 1.1+
        if spec.operator == ">":
            return fmt("{VADJ}+")

        # <= 1 --> <1.1
        if spec.operator == "<=":
            return fmt("<{VADJ}")

        # <1 --> <1
        if spec.operator == "<":
            return fmt("<{V}")

        # ~=1.2 --> 1.2+<2; ~=1.2.3 --> 1.2.3+<1.3
        if spec.operator == "~=":
            v = Version(parsed_rez_ver())
            v = v.trim(len(v) - 1)
            v_next = next_ver(str(v))
            return fmt("{V}+<" + v_next)

        # !=1.* --> <1|2+; !=1.2.* --> <1.2|1.3+
        if spec.operator == "!=" and spec.version.endswith(".*"):
            return fmt("<{V}|{VNEXT}+")

        # !=1 --> <1|1.1+; !=1.2 --> <1.2|1.2.1+
        if spec.operator == "!=":
            return fmt("<{V}|{VADJ}+")

        raise PackageRequestError(
            "Don't know how to convert PEP440 specifier %r "
            "into rez equivalent" % specifier)

    # convert each spec into rez equivalent
    ranges = list(map(convert_spec, specifier))

    # AND together ranges
    total_range = VersionRange(ranges[0])

    for range_ in ranges[1:]:
        range_ = VersionRange(range_)
        total_range = total_range.intersection(range_)

        if total_range is None:
            raise PackageRequestError(
                "PEP440 specifier %r converts to a non-intersecting rez "
                "version range" % specifier)

    return total_range
Exemplo n.º 17
0
    def test_containment(self):
        # basic containment
        self.assertTrue(Version("3") in VersionRange("3+"))
        self.assertTrue(Version("5") in VersionRange("3..5"))
        self.assertTrue(Version("5_") not in VersionRange("3..5"))
        self.assertTrue(Version("3.0.0") in VersionRange("3+"))
        self.assertTrue(Version("3.0.0") not in VersionRange("3.1+"))
        self.assertTrue(Version("3") in VersionRange("<1|5|6|8|7|3|60+"))
        self.assertTrue(Version("3") in VersionRange("<1|5|6|8|7|==3|60+"))
        self.assertTrue(VersionRange("2.1+<4") in VersionRange("<4"))
        self.assertTrue(VersionRange("2.1..4") not in VersionRange("<4"))
        self.assertTrue(VersionRange("3") in VersionRange("3"))
        self.assertTrue(VersionRange("==3") in VersionRange("3"))
        self.assertTrue(VersionRange("3.5+<3_") in VersionRange("3"))
        self.assertTrue(VersionRange("3") not in VersionRange("4+<6"))
        self.assertTrue(VersionRange("3+<10") not in VersionRange("4+<6"))

        # iterating over sorted version list
        numbers = [2, 3, 5, 10, 11, 13, 14]
        versions = [Version(str(x)) for x in numbers]
        rev_versions = list(reversed(versions))
        composite_range = VersionRange.from_versions(versions)

        entries = [(VersionRange(""), 7), (VersionRange("0+"), 7),
                   (VersionRange("5+"), 5), (VersionRange("6+"), 4),
                   (VersionRange("50+"), 0), (VersionRange(">5"), 4),
                   (VersionRange("5"), 1), (VersionRange("6"), 0),
                   (VersionRange("<5"), 2), (VersionRange("<6"), 3),
                   (VersionRange("<50"), 7), (VersionRange("<=5"), 3),
                   (VersionRange("<1"), 0), (VersionRange("2|9+"), 5),
                   (VersionRange("3+<6|12+<13.5"), 3),
                   (VersionRange("<1|20+"), 0), (VersionRange(">0<20"), 7)]

        for range_, count in entries:
            # brute-force containment tests
            matches = set(x for x in versions if x in range_)
            self.assertEqual(len(matches), count)

            # more optimal containment tests
            def _test_it(it):
                matches_ = set(version for contains, version in it if contains)
                self.assertEqual(matches_, matches)

            _test_it(range_.iter_intersect_test(versions))
            _test_it(range_.iter_intersect_test(rev_versions, descending=True))

            # throw in an intersection test
            self.assertEqual(composite_range.intersects(range_), (count != 0))
            int_range = composite_range & range_
            versions_ = [] if int_range is None else int_range.to_versions()
            self.assertEqual(set(versions_), matches)

            # throw in a superset test as well
            self.assertEqual(range_.issuperset(composite_range), (count == 7))
            if count:
                self.assertTrue(composite_range.issuperset(int_range))