def test_cannotdecides(self): # https://github.com/psss/fmf/issues/117 # CannotDecide and True = True and CannotDecide = CannotDecide # CannotDecide and False = False and CannotDecide = False # CannotDecide or True = True or CannotDecide = True # CannotDecide or False = False or CannotDecide = CannotDecide _true = "foo == bar" _false = "foo != bar" _cannot = "baz == bar" env = Context(foo="bar") for a, op, b in [ (_cannot, 'and', _true), (_true, 'and', _cannot), (_cannot, 'or', _false), (_false, 'or', _cannot), ]: exp = "{0} {1} {2}".format(a, op, b) with pytest.raises(CannotDecide): env.matches(exp) for outcome, a, op, b in [ (False, _cannot, 'and', _false), (False, _false, 'and', _cannot), (True, _cannot, 'or', _true), (True, _true, 'or', _cannot), ]: exp = "{0} {1} {2}".format(a, op, b) assert env.matches(exp) == outcome assert env.matches("{0} and {1} or {2}".format(_cannot, _false, _true)) assert not env.matches("{0} or {1} and {2}".format( _false, _cannot, _false))
def test_matches_groups(self): """ and/or in rules with yes/no/cannotdecide outcome """ context = Context(distro="centos-8.2.0") # Clear outcome assert context.matches("distro = centos-8.2.0 or distro = fedora") assert context.matches("distro = fedora or distro = centos-8.2.0") assert not context.matches("distro != centos-8.2.0 or distro = fedora") assert context.matches("distro = centos-8 and distro = centos-8.2") assert not context.matches("distro = centos-8 and distro = centos-8.6") # Some operators cannot be decided assert context.matches("distro = centos-8.2.0 or foo=bar") assert context.matches("foo=bar or distro = centos-8.2.0") assert context.matches( "foo=bar and distro = centos-8.2.0" ) # skip over 'foo' part since it is not defined assert not context.matches("foo=bar and distro = rhel") assert not context.matches("foo=bar or distro = centos-8.9.0") # Whole rule cannot be decided for undecidable in [ "foo = baz", "foo = baz and distro ~<= centos-7.2", # both are CannotDecide "foo = baz and distro ~<= fedora-32 or do=done", ]: with pytest.raises(CannotDecide): context.matches(undecidable)
def test_known_troublemakers(self): """ Do not regress on these expressions """ # From fmf/issues/89: # following is true (missing left values are treated as lower) assert Context(distro='foo-1').matches('distro < foo-1.1') # but only if at least one version part is defined with pytest.raises(CannotDecide): Context(distro='fedora').matches('distro < fedora-33') # so use ~ if you need an explict Major check with pytest.raises(CannotDecide): Context(distro='fedora').matches('distro ~< fedora-33') assert Context(distro='fedora-33').matches('distro == fedora') with pytest.raises(CannotDecide): Context("module = py:5.28").matches("module > perl:5.28") with pytest.raises(CannotDecide): Context("module = py:5").matches("module > perl:5.28") with pytest.raises(CannotDecide): Context("module = py:5").matches("module >= perl:5.28") with pytest.raises(CannotDecide): Context("distro = centos").matches("distro >= fedora") assert Context("distro = centos").matches("distro != fedora") assert not Context("distro = centos").matches("distro == fedora") rhel7 = Context("distro = rhel-7") assert rhel7.matches("distro == rhel") assert rhel7.matches("distro == rhel-7") assert not rhel7.matches("distro == rhel-7.3") assert not rhel7.matches("distro == rhel-7.3.eus") assert rhel7.matches("distro >= rhel-7") assert not rhel7.matches("distro >= rhel-7.3") assert not rhel7.matches("distro >= rhel-7.3.eus") with pytest.raises(CannotDecide): rhel7.matches("distro ~> rhel-7.3") assert not rhel7.matches("distro > rhel") # https://github.com/psss/fmf/pull/128#pullrequestreview-631589335 expr = "distro < fedora-33 or distro < centos-6.9" # Checking `CannotDecide or False` for distro in "fedora-33 fedora-34 centos-7.7".split(): with pytest.raises(CannotDecide): Context(distro=distro).matches(expr) # Checking `CannotDecide or True` assert Context(distro="centos-6.5").matches(expr) assert Context(distro="fedora-32").matches(expr)
def test_known_troublemakers(self): """ Do not regress on these expressions """ # From fmf/issues/89: # following is true (missing left values are treated as lower) assert Context(distro='foo-1').matches('distro < foo-1.1') # but only if at least one version part is defined with pytest.raises(CannotDecide): Context(distro='fedora').matches('distro < fedora-33') # so use ~ if you need an explict Major check with pytest.raises(CannotDecide): Context(distro='fedora').matches('distro ~< fedora-33') assert Context(distro='fedora-33').matches('distro == fedora') with pytest.raises(CannotDecide): Context("module = py:5.28").matches("module > perl:5.28") with pytest.raises(CannotDecide): Context("module = py:5").matches("module > perl:5.28") with pytest.raises(CannotDecide): Context("module = py:5").matches("module >= perl:5.28") with pytest.raises(CannotDecide): Context("distro = centos").matches("distro >= fedora") assert Context("distro = centos").matches("distro != fedora") assert not Context("distro = centos").matches("distro == fedora") rhel7 = Context("distro = rhel-7") assert rhel7.matches("distro == rhel") assert rhel7.matches("distro == rhel-7") assert not rhel7.matches("distro == rhel-7.3") assert not rhel7.matches("distro == rhel-7.3.eus") assert rhel7.matches("distro >= rhel-7") assert not rhel7.matches("distro >= rhel-7.3") assert not rhel7.matches("distro >= rhel-7.3.eus") with pytest.raises(CannotDecide): rhel7.matches("distro ~> rhel-7.3") assert not rhel7.matches("distro > rhel")
def test_module_streams(self): """ How you can use Context for modules """ perl = Context("module = perl:5.28") mix = Context("module = perl:5.28,php:7.3") assert perl.matches("module >= perl:5") assert not perl.matches("module > perl:5") assert perl.matches("module > perl:5.7") assert perl.matches("module >= perl:5.28") assert not perl.matches("module > perl:6") assert not perl.matches("module > perl:6.2") assert not perl.matches("module >= perl:6.2") # Using ~ to compare only within same minor # e.g feature in 5.28+ but dropped in perl6 assert perl.matches("module ~>= perl:5.28") with pytest.raises(CannotDecide): Context("module = perl:6.28").matches("module ~>= perl:5.28")
def test_minor_eq(self): centos = Context(distro="centos-8.2.0") for not_equal in ["fedora", "fedora-3", "centos-7"]: assert not centos.matches("distro ~= {}".format(not_equal)) assert centos.matches("distro ~= centos") assert centos.matches("distro ~= centos-8") assert centos.matches("distro ~= centos-8.2") assert not centos.matches("distro ~= centos-8.3") assert centos.matches("distro ~= centos-8.2.0") assert not centos.matches("distro ~= centos-8.3.0") with pytest.raises(CannotDecide): centos.matches("distro ~= centos-8.2.0.0") multi = Context(distro=["centos-8.2.0", "centos-7.6.0"]) for not_equal in [ "fedora", "fedora-3", "rhel-7", "rhel-7.8.0", "centos-6", "centos-6.5" ]: assert not multi.matches("distro ~= {}".format(not_equal)) assert multi.matches("distro ~= centos") assert multi.matches("distro ~= centos-8") assert not multi.matches("distro ~= centos-8.3") assert multi.matches("distro ~= centos-7") assert multi.matches("distro ~= centos-7.6") assert not multi.matches("distro ~= centos-7.5") multi_rh = Context(distro=["centos-8.2.0", "rhel-8.2.0", "fedora-40"]) assert multi_rh.matches("distro ~= centos") assert multi_rh.matches("distro ~= rhel") assert multi_rh.matches("distro ~= fedora") assert multi_rh.matches("distro ~= centos-8") assert multi_rh.matches("distro ~= rhel-8") assert multi_rh.matches("distro ~= fedora-40") assert not multi_rh.matches("distro ~= centos-9") assert not multi_rh.matches("distro ~= rhel-9") assert not multi_rh.matches("distro ~= fedora-41")
def test_minor_comparison_mode(self): """ How it minor comparison should work """ fedora = Context(distro="fedora-33") centos = Context(distro="centos-7.3.0") centos6 = Context(distro="centos-6.9.0") # Simple version compare is not enough # Think about feature added in centos-7.4.0 and centos-6.9.0 # so for 'centos' Context we want matches() == False # but for 'centos6' we want matches() == True # rule = "distro >= centos-7.4.0" assert not centos.matches(rule) assert not centos6.matches(rule) # we need the opposite outcome rule = "distro >= centos-6.9.0" assert centos.matches(rule) # we need the opposite outcome assert centos6.matches(rule) assert centos.matches( "distro >= centos-7.4.0 or distro >= centos-6.9.0" ) # we need the opposite outcome assert not centos.matches( "distro >= centos-7.4.0 and distro >= centos-6.9.0" ) # we need the opposite outcome assert centos6.matches( "distro >= centos-7.4.0 or distro >= centos-6.9.0") assert not centos6.matches( "distro >= centos-7.4.0 and distro >= centos-6.9.0") # Here comes minor compare into the play as it skip incomparable majors # All outcomes are exactly as we need them to be # tmt uses undecided='skip' so rule is skipped with pytest.raises(CannotDecide): centos.matches( "distro ~>= centos-7.4.0 or distro ~>= centos-6.9.0") assert not centos.matches( "distro ~>= centos-7.4.0 and distro ~>= centos-6.9.0") assert centos6.matches( "distro ~>= centos-7.4.0 or distro ~>= centos-6.9.0") # tmt uses undecided='skip' so rule is skipped with pytest.raises(CannotDecide): centos6.matches( "distro ~>= centos-7.4.0 and distro ~>= centos-6.9.0")
def test_matches(self): """ yes/no/skip test per operator for matches """ context = Context( distro="fedora-32", arch=["x86_64", "ppc64le"], component="bash-5.0.17-1.fc32", ) # defined assert context.matches("distro is defined") assert not context.matches("FOOBAR is defined") # skip not possible for this operator # !defined assert context.matches("FOOBAR is not defined") assert not context.matches("distro is not defined") # skip not possible for this operator # == assert context.matches("distro == fedora-32") assert context.matches("distro == fedora") assert not context.matches("distro == centos-8") with pytest.raises(CannotDecide): context.matches("product == fedora-32") # != assert not context.matches("distro != fedora-32") assert not context.matches("distro != fedora") assert context.matches("distro != centos-8") with pytest.raises(CannotDecide): context.matches("product != fedora-32") # ~= aka major/minor mode assert context.matches("distro ~= fedora") assert context.matches("distro ~= fedora-32") assert not context.matches("distro ~= fedora-45") assert not context.matches( "distro ~= centos-8") # fedora is not centos with pytest.raises(CannotDecide): # dimension product is not defined context.matches("product ~= fedora-32") # '<' assert context.matches("distro < fedora-33") assert not context.matches("distro < fedora-32") with pytest.raises(CannotDecide): context.matches("product < centos-8") # missing version parts are allowed but at least one needs to be defined with pytest.raises(CannotDecide): Context(distro='fedora').matches("distro < fedora-33") assert Context(distro='foo-1').matches("distro < foo-1.1") # '~<': assert Context(distro='centos-8.3').matches("distro ~< centos-8.4") assert context.matches("distro ~< fedora-33") with pytest.raises(CannotDecide): context.matches("distro ~< centos-8") with pytest.raises(CannotDecide): context.matches("product ~< centos-8") assert not context.matches( "distro ~< fedora") # right side ignores major assert not context.matches("distro ~> fedora") # '<=': assert context.matches("distro <= fedora-32") assert context.matches("distro <= fedora") assert not context.matches("distro <= fedora-30") with pytest.raises(CannotDecide): context.matches("product <= centos-8") # '~<=' assert context.matches("distro ~<= fedora-33") assert context.matches("distro ~<= fedora") with pytest.raises(CannotDecide): context.matches("distro ~<= centos-8") with pytest.raises(CannotDecide): context.matches("product ~<= centos-8") # '~!=': assert context.matches("distro ~!= fedora-33") assert not context.matches("distro ~!= fedora-32") assert not context.matches("distro ~!= fedora") assert context.matches("distro ~!= centos-8") with pytest.raises(CannotDecide): context.matches("product ~!= centos-8") # '>=': assert context.matches("distro >= fedora-32") assert context.matches("distro >= fedora") assert not context.matches("distro >= fedora-40") with pytest.raises(CannotDecide): context.matches("product >= centos-8") # '~>=': assert context.matches("distro ~>= fedora-32") assert context.matches("distro ~>= fedora") assert not context.matches("distro ~>= fedora-33") with pytest.raises(CannotDecide): context.matches("distro ~>= centos-8") with pytest.raises(CannotDecide): context.matches("product ~>= centos-8") # '>': assert context.matches("distro > fedora-30") assert not context.matches("distro > fedora-40") assert not context.matches("distro > fedora") with pytest.raises(CannotDecide): context.matches("product > centos-8") # '~>': assert context.matches("distro ~> fedora-30") assert not context.matches("distro ~> fedora-42") with pytest.raises(CannotDecide): context.matches("distro ~> centos-8") with pytest.raises(CannotDecide): context.matches("product ~> centos-8") assert not context.matches("distro ~> fedora")
def test_right_side_defines_precision_tilda(self): """ Right side defines how many version parts need to match (~ operations) """ bar_830 = Context(dimension="bar-8.3.0") bar_800 = Context(dimension="bar-8.0.0") bar_ = Context(dimension="bar") # missing major bar_8 = Context(dimension="bar-8") # so essentially bar-8.0.0 # these are equal for value in "bar bar-8 bar-8.3 bar-8.3.0".split(): for op in "~= ~<= ~>=".split(): assert bar_830.matches('dimension {0} {1}'.format(op, value)) for op in "~< ~> ~!=".split(): if value == 'bar' and op != '~!=': continue assert not bar_830.matches('dimension {0} {1}'.format( op, value)) # value prefixed so name doesn't match -> not equal assert not bar_830.matches('dimension ~= prefix_{0}'.format(value)) assert bar_830.matches('dimension ~!= prefix_{0}'.format(value)) # value prefix so name doesn't match -> cannot be compared for op in "~<= ~>=".split(): with pytest.raises(CannotDecide): bar_830.matches('dimension {0} prefix_{1}'.format( op, value)) # different major with minor comparison for value in "bar-7.2 bar-7.2.0".split(): for op in "~< ~<= ~> ~>=".split(): with pytest.raises(CannotDecide): bar_830.matches("dimension {0} {1}".format(op, value)) # no minor compare required, so major comparison is allowed for op in "~< ~<=".split(): assert not bar_830.matches("dimension {0} bar-7".format(op)) for op in "~> ~>=".split(): assert bar_830.matches("dimension {0} bar-7".format(op)) # these are newer for value in "bar-8.4 bar-8.4.0".split(): for op in "~> ~>= ~=".split(): assert not bar_830.matches('dimension {0} {1}'.format( op, value)) for op in "~< ~<= ~!=".split(): assert bar_830.matches('dimension {0} {1}'.format(op, value)) # missing enough data to decide for value in "bar-8 bar-8.3 bar-8.3.0".split(): for op in "~= ~<= ~>= ~< ~> ~!=".split(): with pytest.raises(CannotDecide): bar_.matches("dimension {0} {1}".format(op, value)) if value != "bar-8": with pytest.raises(CannotDecide): bar_8.matches("dimension {0} {1}".format(op, value))
def test_right_side_defines_precision(self): """ Right side defines how many version parts need to match """ bar_830 = Context(dimension="bar-8.3.0") bar_800 = Context(dimension="bar-8.0.0") bar_ = Context(dimension="bar") # so essentially bar-0.0.0 bar_8 = Context(dimension="bar-8") # so essentially bar-8.0.0 # these are equal for value in "bar bar-8 bar-8.3 bar-8.3.0".split(): for op in "== <= >=".split(): assert bar_830.matches('dimension {0} {1}'.format(op, value)) for op in "< > !=".split(): if value == 'bar' and op != '!=': continue assert not bar_830.matches('dimension {0} {1}'.format( op, value)) # value prefixed so name doesn't match -> not equal assert not bar_830.matches('dimension == prefix_{0}'.format(value)) assert bar_830.matches('dimension != prefix_{0}'.format(value)) # value prefix so name doesn't match -> cannot be compared for op in "<= >=".split(): with pytest.raises(CannotDecide): bar_830.matches('dimension {0} prefix_{1}'.format( op, value)) # valid comparison for value in "bar-7 bar-7.2 bar-8.1 bar-7.2.0 bar-8.1.0".split(): for op in "< <= ==".split(): assert not bar_830.matches("dimension {0} {1}".format( op, value)) for op in "> >= !=".split(): assert bar_830.matches("dimension {0} {1}".format(op, value)) assert bar_.matches("dimension != {0}".format(value)) assert not bar_.matches("dimension == {0}".format(value)) # these are newer for value in "bar-9 bar-9.2 bar-9.2.0".split(): for op in "> >= ==".split(): assert not bar_830.matches('dimension {0} {1}'.format( op, value)) for op in "< <= !=".split(): assert bar_830.matches('dimension {0} {1}'.format(op, value)) # cannot be compared for op in "< <= > >=".split(): with pytest.raises(CannotDecide): bar_.matches("dimension {0} {1}".format(op, value))
def test_comma(self): """ Comma is sugar for OR """ con = Context(single="foo", multi=["first", "second"]) # First as longer line, then using comma assert con.matches("single == foo or single == bar") assert con.matches("single == foo, bar") assert not con.matches("single == baz or single == bar") assert not con.matches("single == baz, bar") assert con.matches("single != foo or single != bar") assert con.matches("single != foo, bar") # And now with multiple values in the dimension assert con.matches("multi == first, value") assert con.matches("multi == second, value") assert con.matches("multi != third, value") # True because each first vs second value compare is false assert con.matches("multi != first, second") assert con.matches("multi != first or multi != second") # More real-life example distro = Context(distro="centos-stream-8") assert distro.matches("distro < centos-stream-9, fedora-34") assert not distro.matches("distro < fedora-34, centos-stream-8")