#4 Add tests for Expr comparison and PrefixRelOp
Merged 4 years ago by jjames. Opened 4 years ago by defolos.
defolos/opam2rpm add_expression_tests  into  master

file modified
+92 -61
@@ -268,6 +268,7 @@ 

  

          return self.strRepr

  

+ 

  class Token:

      """A token in an opam file.

  
@@ -278,24 +279,45 @@ 

  

      __slots__ = ['tok_type', 'value']

  

-     def __init__(self, tok_type, value):

+     _NEGATED_TYPE: Dict[TokenType, TokenType] = {

+         TokenType.EQ: TokenType.NEQ,

+         TokenType.NEQ: TokenType.EQ,

+ 

+         TokenType.GEQ: TokenType.LT,

+         TokenType.LT: TokenType.GEQ,

+ 

+         TokenType.GT: TokenType.LEQ,

+         TokenType.LEQ: TokenType.GT

+     }

+ 

+     def __init__(self, tok_type: TokenType, value) -> None:

          """Initialize an opam token with its type and value."""

-         self.tok_type = tok_type

+         self.tok_type: TokenType = tok_type

          self.value = value

  

-     def __str__(self):

+     def __eq__(self, other: object) -> bool:

+         return (

+             (self.tok_type == other.tok_type) and (self.value == other.value)

+         ) if isinstance(other, Token) else False

+ 

+     def __str__(self) -> str:

          """Produce a string representation of an opam token."""

          return 'Token(' + self.tok_type.name + ', ' + str(self.value) + ')'

  

-     def __repr__(self):

+     def __repr__(self) -> str:

          """Produce a string representation of an opam token."""

          return 'Token(' + self.tok_type.name + ', ' + repr(self.value) + ')'

  

-     def simplify(self, values, internal, seen):

+     def simplify(self, values, internal, seen) -> Token:

          """Simplify a token."""

          return self

  

- def set_bool_true():

+     def negate(self) -> Token:

+         return self if self.tok_type not in Token._NEGATED_TYPE else \

+             Token(Token._NEGATED_TYPE[self.tok_type], self.value)

+ 

+ 

+ def set_bool_true() -> Token:

      """Create a boolean token with the value of true."""

      return Token(TokenType.BOOL, True)

  
@@ -419,9 +441,9 @@ 

          """Initialize a boolean value."""

          self.val = val

  

-     def __eq__(self, other):

+     def __eq__(self, other: object) -> bool:

          """Compare two boolean values for equality."""

-         return self.val == other.val

+         return self.val == other.val if isinstance(other, BoolVal) else False

  

      def __hash__(self):

          """Compute a hash code for a boolean value."""
@@ -457,7 +479,7 @@ 

  

      def __eq__(self, other):

          """Compare two integer values for equality."""

-         return self.val == other.val

+         return self.val == other.val if isinstance(other, IntVal) else False

  

      def __hash__(self):

          """Compute a hash code for a integer value."""
@@ -512,9 +534,9 @@ 

          self.val = val

          self.matcher = re.compile('%{(.*)}%')

  

-     def __eq__(self, other):

+     def __eq__(self, other: object) -> bool:

          """Compare two string values for equality."""

-         return other is not None and self.val == other.val

+         return self.val == other.val if isinstance(other, StrVal) else False

  

      def __hash__(self):

          """Compute a hash code for a string value."""
@@ -586,9 +608,19 @@ 

          """Initialize a list value."""

          self.val = val

  

-     def __eq__(self, other):

+     def __len__(self) -> int:

+         return len(self.val)

+ 

+     def __iter__(self) -> Iterator[Expr]:

+         for elem in self.val:

+             yield elem

+ 

+     def __getitem__(self, index: int) -> Expr:

+         return self.val[index]

+ 

+     def __eq__(self, other: object) -> bool:

          """Compare two list values for equality."""

-         return self.val == other.val

+         return self.val == other.val if isinstance(other, ListVal) else False

  

      def __str__(self):

          """Produce a string representation of a list value."""
@@ -651,9 +683,9 @@ 

          """Initialize a dictionary value."""

          self.val = val

  

-     def __eq__(self, other):

+     def __eq__(self, other: object) -> bool:

          """Compare two dictionary values for equality."""

-         return self.val == other.val

+         return self.val == other.val if isinstance(other, DictVal) else False

  

      def __str__(self):

          """Produce a string representation of a dictionary value."""
@@ -681,9 +713,9 @@ 

          """Initialize an identifier."""

          self.name = name

  

-     def __eq__(self, other):

+     def __eq__(self, other) -> bool:

          """Compare two opam identifiers for equality."""

-         return self.name == other.name

+         return self.name == other.name if isinstance(other, Identifier) else False

  

      def __hash__(self):

          """Compute a hash code for an opam identifier."""
@@ -719,9 +751,10 @@ 

          """Initialize a NOT operator."""

          self.term = term

  

-     def __eq__(self, other):

+     def __eq__(self, other: object) -> bool:

          """Compare two NOT operators for equality."""

-         return self.term == other.term

+         return self.term == other.term if isinstance(other, NotOp) \

+             else False

  

      def __hash__(self):

          """Compute a hash code for a NOT operator."""
@@ -754,7 +787,8 @@ 

  

      def __eq__(self, other):

          """Compare two defined operators for equality."""

-         return self.term == other.term

+         return self.term == other.term if isinstance(other, DefinedOp) \

+             else False

  

      def __hash__(self):

          """Compute a hash code for a defined operator."""
@@ -781,14 +815,15 @@ 

  

      __slots__ = ['oper', 'term']

  

-     def __init__(self, oper, term):

+     def __init__(self, operator: Token, term: Expr) -> None:

          """Initialize a prefix relational operator."""

-         self.oper = oper

+         self.oper = operator

          self.term = term

  

-     def __eq__(self, other):

+     def __eq__(self, other: object) -> bool:

          """Compare two prefix relational operators for equality."""

-         return self.oper == other.oper and self.term == other.term

+         return self.oper == other.oper and self.term == other.term if \

+             isinstance(other, PrefixRelOp) else False

  

      def __hash__(self):

          """Compute a hash code for a prefix relational operator."""
@@ -796,39 +831,27 @@ 

  

      def __str__(self):

          """Produce a string representation of a prefix relational operator."""

-         return self.oper + ' ' + str(self.term)

+         return self.oper.value + ' ' + str(self.term)

  

      def __repr__(self):

          """Produce a string representation of a prefix relational operator."""

-         return 'PrefixRelOp(' + self.oper + ', ' + repr(self.term) + ')'

+         return 'PrefixRelOp(' + self.oper.value + ', ' + repr(self.term) + ')'

  

-     def verdependency(self, name):

+     def verdependency(self, name: str) -> str:

          """Convert a prefix relational operator to an RPM dependency."""

-         return name + ' ' + self.oper + ' ' \

+         return name + ' ' + self.oper.value + ' ' \

              + version.Version(str(self.term)).to_fedora()[0]

  

-     def negate(self):

+     def negate(self) -> PrefixRelOp:

          """Negate a prefix relational operator."""

-         if self.oper.tok_type == TokenType.EQ:

-             neg = PrefixRelOp(TokenType.NEQ, self.term)

-         elif self.oper.tok_type == TokenType.NEQ:

-             neg = PrefixRelOp(TokenType.EQ, self.term)

-         elif self.oper.tok_type == TokenType.GEQ:

-             neg = PrefixRelOp(TokenType.LT, self.term)

-         elif self.oper.tok_type == TokenType.GT:

-             neg = PrefixRelOp(TokenType.LEQ, self.term)

-         elif self.oper.tok_type == TokenType.LEQ:

-             neg = PrefixRelOp(TokenType.GT, self.term)

-         elif self.oper.tok_type == TokenType.LT:

-             neg = PrefixRelOp(TokenType.GEQ, self.term)

-         else:

-             neg = self

-         return neg

+         return PrefixRelOp(self.oper.negate(), self.term)

  

-     def simplify(self, values, internal, seen):

+     def simplify(self, values, internal, seen) -> Optional[PrefixRelOp]:

          """Simplify a prefix relational operator."""

-         return PrefixRelOp(self.oper,

-                            self.term.simplify(values, internal, seen))

+         simplified_term = self.term.simplify(values, internal, seen)

+         return PrefixRelOp(self.oper, simplified_term) \

+             if simplified_term is not None else None

+ 

  

  class RelOp(Expr):

      """Class representing a relational operator."""
@@ -843,9 +866,11 @@ 

  

      def __eq__(self, other):

          """Compare two relational operators for equality."""

-         return self.oper == other.oper \

-             and self.left == other.left \

+         return (

+             self.oper == other.oper

+             and self.left == other.left

              and self.right == other.right

+         ) if isinstance(other, RelOp) else False

  

      def __hash__(self):

          """Compute a hash code for a relational operator."""
@@ -903,9 +928,10 @@ 

          self.left = left

          self.right = right

  

-     def __eq__(self, other):

+     def __eq__(self, other: object) -> bool:

          """Compare two AND operators for equality."""

-         return self.left == other.left and self.right == other.right

+         return self.left == other.left and self.right == other.right if \

+             isinstance(other, AndOp) else False

  

      def __hash__(self):

          """Compute a hash code for an AND operator."""
@@ -965,9 +991,10 @@ 

          self.left = left

          self.right = right

  

-     def __eq__(self, other):

+     def __eq__(self, other: object) -> bool:

          """Compare two OR operators for equality."""

-         return self.left == other.left and self.right == other.right

+         return (self.left == other.left and self.right == other.right) \

+             if isinstance(other, OrOp) else False

  

      def __hash__(self):

          """Compute a hash code for an OR operator."""
@@ -1028,11 +1055,13 @@ 

          self.var = var

          self.value = value

  

-     def __eq__(self, other):

+     def __eq__(self, other: object) -> bool:

          """Compare two environment update operators for equality."""

-         return self.oper == other.oper \

-             and self.var == other.var \

+         return (

+             self.oper == other.oper

+             and self.var == other.var

              and self.value == other.value

+         ) if isinstance(other, EnvOp) else False

  

      def __hash__(self):

          """Compute a hash code for an OR operator."""
@@ -1062,9 +1091,10 @@ 

          self.name = name

          self.body = body

  

-     def __eq__(self, other):

+     def __eq__(self, other: object) -> bool:

          """Compare two opam options for equality."""

-         return self.name == other.name and self.body == other.body

+         return self.name == other.name and self.body == other.body if \

+             isinstance(other, Option) else False

  

      def __hash__(self):

          """Compute a hash code for an opam option."""
@@ -1118,9 +1148,10 @@ 

          self.name = name

          self.body = body

  

-     def __eq__(self, other):

+     def __eq__(self, other: object) -> bool:

          """Compare two opam file sections for equality."""

-         return self.name == other.name and self.body == other.body

+         return (self.name == other.name and self.body == other.body) \

+             if isinstance(other, Section) else False

  

      def __hash__(self):

          """Compute a hash code for an opam file section."""
@@ -1215,7 +1246,7 @@ 

          elif tok.tok_type == TokenType.DEFINED:

              value = DefinedOp(self.parse_option_value(toks))

          elif is_rel_op(tok.tok_type):

-             value = PrefixRelOp(tok.value, self.parse_option_value(toks))

+             value = PrefixRelOp(tok, self.parse_option_value(toks))

          elif is_atom(tok):

              if tok.tok_type == TokenType.BOOL:

                  tok = get_bool(tok.value)

@@ -0,0 +1,116 @@ 

+ from opam2rpm.opamparser import (

+     TokenType,

+     Token,

+     BoolVal,

+     IntVal,

+     StrVal,

+     ListVal,

+     DictVal,

+     Identifier,

+     NotOp,

+     DefinedOp,

+     PrefixRelOp,

+     RelOp,

+     AndOp,

+     OrOp,

+     EnvOp,

+     Option,

+     Section,

+ )

+ 

+ 

+ EXPRESSIONS = [

+     None,

+     1,

+     2,

+     "asdf",

+     BoolVal(True),

+     BoolVal(False),

+     IntVal(1),

+     IntVal(-512),

+     StrVal(""),

+     StrVal("bar"),

+     ListVal([]),

+     ListVal([StrVal("baz")]),

+     DictVal({"a": 1}),

+     DictVal({}),

+     Identifier("foo"),

+     Identifier("with-doc"),

+     NotOp(StrVal("a")),

+     NotOp(IntVal(1)),

+     DefinedOp(StrVal("with-foo")),

+     DefinedOp(Option(StrVal("build"), IntVal(3))),

+     PrefixRelOp(Token(TokenType.GEQ, ">="), StrVal("libfoo")),

+     PrefixRelOp(Token(TokenType.NOT, "!"), BoolVal(True)),

+     RelOp(Token(TokenType.LT, "<"), IntVal(1), IntVal(2)),

+     RelOp(Token(TokenType.GT, ">"), IntVal(1), IntVal(4)),

+     AndOp(StrVal("foo"), IntVal(1)),

+     AndOp(

+         RelOp(Token(TokenType.NEQ, "!="), StrVal("3"), IntVal(-18)),

+         BoolVal(True),

+     ),

+     OrOp(StrVal("libfoo"), StrVal("libbar")),

+     OrOp(IntVal(1), StrVal("baz")),

+     EnvOp(Token(TokenType.OR, "|"), StrVal("bar"), IntVal(16)),

+     EnvOp(Token(TokenType.AND, "&"), IntVal(2), IntVal(42)),

+     Option(StrVal("dune"), Identifier("with-dependency")),

+     Option(BoolVal(True), IntVal(-42)),

+     Section(StrVal("build"), ListVal([StrVal("build")])),

+     Section(StrVal("clean"), IntVal(8)),

+ ]

+ 

+ 

+ def test_cross_comparisons():

+     for i, expr_i in enumerate(EXPRESSIONS):

+         for j, expr_j in enumerate(EXPRESSIONS):

+             if i == j:

+                 assert expr_i == expr_j

+             else:

+                 assert expr_i != expr_j

+ 

+ 

+ class TestPrefixRelOp:

+     def test_negate(self):

+         term = StrVal("irrelevant")

+         # = -> !=

+         assert PrefixRelOp(

+             Token(TokenType.EQ, "="), term

+         ).negate() == PrefixRelOp(Token(TokenType.NEQ, "="), term)

+         assert PrefixRelOp(

+             Token(TokenType.NEQ, "!="), term

+         ).negate() == PrefixRelOp(Token(TokenType.EQ, "!="), term)

+ 

+         # >= -> <

+         assert PrefixRelOp(

+             Token(TokenType.GEQ, ">="), term

+         ).negate() == PrefixRelOp(Token(TokenType.LT, ">="), term)

+         assert PrefixRelOp(

+             Token(TokenType.LT, "<"), term

+         ).negate() == PrefixRelOp(Token(TokenType.GEQ, "<"), term)

+ 

+         # <= -> >

+         assert PrefixRelOp(

+             Token(TokenType.LEQ, "<="), term

+         ).negate() == PrefixRelOp(Token(TokenType.GT, "<="), term)

+         assert PrefixRelOp(

+             Token(TokenType.GT, ">"), term

+         ).negate() == PrefixRelOp(Token(TokenType.LEQ, ">"), term)

+ 

+         # anything else:

+         assert PrefixRelOp(

+             Token(TokenType.NOT, "!"), term

+         ).negate() == PrefixRelOp(Token(TokenType.NOT, "!"), term)

+ 

+     def test_version(self):

+         assert (

+             PrefixRelOp(

+                 Token(TokenType.EQ, "="), StrVal("16.1")

+             ).verdependency("foo")

+             == "foo = 16.1"

+         )

+         assert (

+             PrefixRelOp(

+                 Token(TokenType.LT, "<"), StrVal("asdf")

+             ).verdependency("libinvalid")

+             == "libinvalid < 0"

+         )

  • add type hints
  • make Expr child classes' eq more robust
  • pass the full token into PrefixRelOp's constructor
  • add negate function to Token class
  • implement List protocol for ListVal

please note that this also contains the changes from https://pagure.io/opam2rpm/pull-request/3 (I'll rebase once that one is addressed)

rebased onto d17d402

4 years ago

Nice! Thank you for the great improvements and tests.

Pull-Request has been merged by jjames

4 years ago