Exemple #1
0
 def SetUp(self):
     aliases = {
         'i': resource_lex.Lexer('integer').Key(),
         'v': resource_lex.Lexer('compound.string.value').Key(),
     }
     self.defaults = resource_projection_parser.Parse(
         '(compound.string:alias=s, floating:alias=f)', aliases=aliases)
     self.rewrite = resource_filter_scrub.Backend().Rewrite
    def get_field(self, field_name, unused_args, unused_kwargs):
        r"""Returns the value of field_name for string.Formatter.format().

    Args:
      field_name: The format string field name to get in the form
        name - the value of name in the payload, '' if undefined
        name?FORMAT - if name is non-empty then re-formats with FORMAT, where
          {?} is the value of name. For example, if name=NAME then
          {name?\nname is "{?}".} expands to '\nname is "NAME".'.
        .a.b.c - the value of a.b.c in the JSON decoded payload contents.
          For example, '{.errors.reason?[{?}]}' expands to [REASON] if
          .errors.reason is defined.
      unused_args: Ignored.
      unused_kwargs: Ignored.

    Returns:
      The value of field_name for string.Formatter.format().
    """
        field_name = _Expand(field_name)
        if field_name == '?':
            return self._value, field_name
        parts = field_name.split('?', 1)
        subparts = parts.pop(0).split(':', 1)
        name = subparts.pop(0)
        printer_format = subparts.pop(0) if subparts else None
        recursive_format = parts.pop(0) if parts else None
        if '.' in name:
            if name.startswith('.'):
                # Only check self.content.
                check_payload_attributes = False
                name = name[1:]
            else:
                # Check the payload attributes first, then self.content.
                check_payload_attributes = True
            key = resource_lex.Lexer(name).Key()
            content = self.content
            if check_payload_attributes and key:
                value = self.__dict__.get(key[0], None)
                if value:
                    content = {key[0]: value}
            value = resource_property.Get(content, key, None)
        elif name:
            value = self.__dict__.get(name, None)
        else:
            value = None
        if not value and not isinstance(value, (int, float)):
            return '', name
        if printer_format or not isinstance(
                value,
            (six.text_type, six.binary_type, float) + six.integer_types):
            buf = io.StringIO()
            resource_printer.Print(value,
                                   printer_format or 'default',
                                   out=buf,
                                   single=True)
            value = buf.getvalue().strip()
        if recursive_format:
            self._value = value
            value = self.format(_Expand(recursive_format))  # pytype: disable=wrong-arg-types
        return value, name
 def Run(self, args):
     if args.json_file:
         with open(args.json_file, 'r') as f:
             resources = json.load(f)
     else:
         resources = json.load(sys.stdin)
     # TODO(gsfowler): Drop this if when the --aggregate global flag lands.
     if args.aggregate:
         key = resource_lex.Lexer(args.aggregate).Key()
         resources = Aggregator(resources, key)
     # TODO(gsfowler): Return resources here when the --filter global flag lands.
     if not args.format:
         args.format = 'json'
     if not args.filter:
         return resources
     select = resource_filter.Compile(args.filter).Evaluate
     filtered_resources = []
     if resource_property.IsListLike(resources):
         for resource in resources:
             if select(resource):
                 filtered_resources.append(resource)
     elif select(resources):
         # treat non-iterable resources as a list of length 1
         filtered_resources.append(resources)
     return filtered_resources
Exemple #4
0
    def _AddFlattenTap(self):
        """Taps one or more resource flatteners into self.resources if needed."""
        def _Slice(key):
            """Helper to add one flattened slice tap."""
            tap = display_taps.Flattener(key)
            # Apply the flatteners from left to right so the innermost flattener
            # flattens the leftmost slice. The outer flatteners can then access
            # the flattened keys to the left.
            self._resources = peek_iterable.Tapper(self._resources, tap)

        keys = self._GetFlag('flatten')
        if not keys:
            return
        for key in keys:
            flattened_key = []
            sliced = False
            for k in resource_lex.Lexer(key).Key():
                if k is None:
                    sliced = True
                    _Slice(flattened_key)
                else:
                    sliced = False
                    flattened_key.append(k)
            if not sliced:
                _Slice(flattened_key)
 def SetUp(self):
     symbols = {'len': len, 'test_transform': lambda x: 'test'}
     aliases = {'y': resource_lex.Lexer('a.b.c').KeyWithAttribute()}
     self.defaults = resource_projection_parser.Parse(
         '(compound.string:alias=s, floating:alias=z)',
         aliases=aliases,
         symbols=symbols)
Exemple #6
0
    def Parse(self, expression, aliases=None):
        """Parses a resource list filter expression.

    This is a hand-rolled recursive descent parser based directly on the
    left-factorized BNF grammar in the file docstring. The parser is not thread
    safe. Each thread should use distinct _Parser objects.

    Args:
      expression: A resource list filter expression string.
      aliases: Resource key alias dictionary.

    Raises:
      ExpressionSyntaxError: The expression has a syntax error.

    Returns:
      tree: The backend expression tree.
    """
        self._lex = resource_lex.Lexer(expression, aliases=aliases)
        tree = self._ParseExpr()
        if not self._lex.EndOfInput():
            raise resource_exceptions.ExpressionSyntaxError(
                'Unexpected tokens [{0}] in expression.'.format(
                    self._lex.Annotate()))
        self._lex = None
        return tree or self._backend.ExprTRUE()
Exemple #7
0
    def get_field(self, field_name, unused_args, unused_kwargs):
        r"""Returns the value of field_name for string.Formatter.format().

    Args:
      field_name: The format string field name to get in the form
        name - the value of name in the payload, '' if undefined
        name?FORMAT - if name is non-empty then re-formats with FORMAT, where
          {?} is the value of name. For example, if name=NAME then
          {name?\nname is "{?}".} expands to '\nname is "NAME".'. ':' may not
          appear in FORMAT, use {?COLON?} instead.
        .a.b.c - the value of a.b.c in the JSON decoded payload contents.
          For example, '{.errors.reason?[{?}]}' expands to [REASON] if
          .errors.reason is defined.
      unused_args: Ignored.
      unused_kwargs: Ignored.

    Returns:
      The value of field_name for string.Formatter.format().
    """
        if field_name.startswith('?'):
            if field_name == '?':
                return self._value, field_name
            if field_name == '?COLON?':
                return ':', field_name
        parts = field_name.split('?', 1)
        name = parts.pop(0)
        fmt = parts.pop(0) if parts else None
        if '.' in name:
            if name.startswith('.'):
                # Only check self.content.
                check_payload_attributes = False
                name = name[1:]
            else:
                # Check the payload attributes first, then self.content.
                check_payload_attributes = True
            key = resource_lex.Lexer(name).Key()
            content = self.content
            if check_payload_attributes and key:
                value = self.__dict__.get(key[0], None)
                if value:
                    content = {key[0]: value}
            value = resource_property.Get(content, key, '')
        elif name:
            value = self.__dict__.get(name, '')
        else:
            value = ''
        if not value and not isinstance(value, (int, float)):
            return '', name
        if not isinstance(value, (basestring, int, float)):
            buf = StringIO.StringIO()
            resource_printer.Print(value, 'default', out=buf, single=True)
            value = buf.getvalue().strip()
        if fmt:
            self._value = value
            value = self.format(fmt)
        return value, name
 def Parenthesize(self, expression):
   """Returns expression enclosed in (...) if it contains AND/OR."""
   # Check for unparenthesized AND|OR.
   lex = resource_lex.Lexer(expression)
   while True:
     tok = lex.Token(' ()', balance_parens=True)
     if not tok:
       break
     if tok in ['AND', 'OR']:
       return '({expression})'.format(expression=expression)
   return expression
 def RunSubTest(self, fun, expression, aliases=None, annotate=None, **kwargs):
   if aliases:
     defaults = resource_projection_spec.ProjectionSpec(None, aliases=aliases)
   else:
     defaults = None
   lex = resource_lex.Lexer(expression, defaults=defaults)
   try:
     actual = getattr(lex, fun)(**kwargs)
   finally:
     if annotate is not None:
       self.AddFollowOnTest('annotate', annotate, lex.Annotate)
   return actual
    def Parse(self, expression=None):
        """Parse a projection expression.

    An empty projection is OK.

    Args:
      expression: The resource projection expression string.

    Raises:
      ExpressionSyntaxError: The expression has a syntax error.

    Returns:
      A ProjectionSpec for the expression.
    """
        self._root = self._projection.GetRoot()
        if not self._root:
            self._root = self._Tree(self._Attribute(self._projection.DEFAULT))
            self._projection.SetRoot(self._root)
        self._projection.SetEmpty(
            self._Tree(self._Attribute(self._projection.PROJECT)))
        if expression:
            self._lex = resource_lex.Lexer(expression, self._projection)
            defaults = False
            self.__key_attributes_only = False
            while self._lex.SkipSpace():
                if self._lex.IsCharacter('('):
                    if not self.__key_attributes_only:
                        defaults = False
                        self._projection.Defaults()
                    self._ParseKeys()
                    if self.__key_attributes_only:
                        self.__key_attributes_only = False
                        self._Reorder()
                elif self._lex.IsCharacter('['):
                    self._ParseAttributes()
                elif self._lex.IsCharacter(':'):
                    self.__key_attributes_only = True
                    self.__key_order_offset = 0
                else:
                    here = self._lex.GetPosition()
                    name = self._lex.Token(':([')  # type: str
                    if not name.isalpha():
                        raise resource_exceptions.ExpressionSyntaxError(
                            'Name expected [{0}].'.format(
                                self._lex.Annotate(here)))
                    self._projection.SetName(name)
                    defaults = True
            self._lex = None
            if defaults:
                self._projection.Defaults()
        return self._projection
Exemple #11
0
 def _AddFlattenTap(self):
   """Taps one or more resource flatteners into self.resources if needed."""
   keys = self._GetFlag('flatten')
   if not keys:
     return
   for key in keys:
     flattened_key = []
     for k in resource_lex.Lexer(key).Key():
       if k is None:
         # None represents a [] slice in resource keys.
         tap = display_taps.Flattener(flattened_key)
         # Apply the flatteners from left to right so the innermost flattener
         # flattens the leftmost slice. The outer flatteners can then access
         # the flattened keys to the left.
         self._resources = peek_iterable.Tapper(self._resources, tap)
       else:
         flattened_key.append(k)
Exemple #12
0
    def Parse(self, expression=None):
        """Parse a projection expression.

    An empty projection is OK.

    Args:
      expression: The resource projection expression string.

    Raises:
      SyntaxError: The expression has a syntax error.

    Returns:
      A ProjectionSpec for the expression.
    """
        self._root = self._projection.GetRoot()
        if not self._root:
            self._root = self._Tree(self._Attribute(self._projection.DEFAULT))
            self._projection.SetRoot(self._root)
        self._projection.SetEmpty(
            self._Tree(self._Attribute(self._projection.PROJECT)))
        if expression:
            self._lex = resource_lex.Lexer(expression,
                                           aliases=self._projection.aliases)
            while self._lex.SkipSpace():
                self.__key_attributes_only = self._lex.IsCharacter(':')
                if self._lex.IsCharacter('('):
                    if not self.__key_attributes_only:
                        self._projection.Defaults()
                        self._ordinal = 0
                    self._ParseKeys()
                elif self._lex.IsCharacter('['):
                    self._ParseAttributes()
                else:
                    here = self._lex.GetPosition()
                    name = self._lex.Token('([')
                    if not name.isalpha():
                        raise SyntaxError('Unexpected tokens [{0}].'.format(
                            self._lex.Annotate(here)))
                    self._projection.SetName(name)
            self._lex = None
        return self._projection
Exemple #13
0
    def _GetSortKeys(self):
        """Returns the list of --sort-by [(key, reverse)] tuples.

    Returns:
      The list of --sort-by [(key, reverse)] tuples, None if --sort-by was not
      specified. The keys are ordered from highest to lowest precedence.
    """
        if not self._GetFlag('sort_by'):
            return None
        keys = []
        for name in self._args.sort_by:
            # ~name reverses the sort for name.
            if name.startswith('~'):
                name = name.lstrip('~')
                reverse = True
            else:
                reverse = False
            # Slices default to the first list element for consistency.
            name = name.replace('[]', '[0]')
            keys.append((resource_lex.Lexer(name).Key(), reverse))
        return keys
Exemple #14
0
    def _GetField(self, name):
        """Gets the value corresponding to name in self.content or class attributes.

    If `name` starts with a period, treat it as a key in self.content and get
    the corresponding value. Otherwise get the value of the class attribute
    named `name` first and fall back to checking keys in self.content.

    Args:
      name (str): The name of the attribute to return the value of.

    Returns:
      A tuple where the first value is `name` with any leading periods dropped,
      and the second value is the value of a class attribute or key in
      self.content.
    """
        if '.' in name:
            if name.startswith('.'):
                # Only check self.content.
                check_payload_attributes = False
                name = name[1:]
            else:
                # Check the payload attributes first, then self.content.
                check_payload_attributes = True
            key = resource_lex.Lexer(name).Key()
            content = self.content
            if check_payload_attributes and key:
                value = self.__dict__.get(key[0], None)
                if value:
                    content = {key[0]: value}
            value = resource_property.Get(content, key, None)
        elif name:
            value = self.__dict__.get(name, None)
        else:
            value = None

        return name, value
Exemple #15
0
def _GetParsedKey(key):
    """Returns a parsed key from a dotted key string."""
    # pylint: disable=g-import-not-at-top, circular dependency
    from googlecloudsdk.core.resource import resource_lex
    return resource_lex.Lexer(key).Key()
  def testLexKey(self):

    def T(expected, expression, aliases=None, annotate=None,
          exception=None, **kwargs):
      self.Run(expected, 'Key', expression, aliases=aliases,
               annotate=annotate, depth=2, exception=exception, **kwargs)

    aliases = {
        'r': resource_lex.Lexer('r.r').KeyWithAttribute(),
        'x': resource_lex.Lexer('a.b.c').KeyWithAttribute(),
        'y': resource_lex.Lexer('a.b[].c').KeyWithAttribute(),
        'z': resource_lex.Lexer('a[123].b.c').KeyWithAttribute(),
        }

    # empty expression

    T([], '')

    # top level resource expressions

    T([], '.', annotate='. *HERE*')
    T([], '.:abc', annotate='. *HERE* :abc')

    # valid keys

    T(['a', 'b', 'c'], ' a.b.c next ', annotate=' a.b.c *HERE* next ')
    T(['a', 'b'], ' a. b next ', annotate=' a. b *HERE* next ')
    T(['a', 'b', None, 'c'], ' a.b[].c next ', annotate=' a.b[].c *HERE* next ')
    T(['a', 123, 'b', 'c'], ' a[123].b.c next ',
      annotate=' a[123].b.c *HERE* next ')
    T(['a', 'b', 'c'], ' a.b.c .d next ', annotate=' a.b.c *HERE* .d next ')
    T(['@type'], '@type:a', annotate='@type *HERE* :a')

    # [x] and x. or .x are equivalent in all combinations

    T(['a', 'b', 'c'], ' a.b[c] next ', annotate=' a.b[c] *HERE* next ')
    T(['a', 'b', 'c'], ' a[b].c next ', annotate=' a[b].c *HERE* next ')
    T(['a', 'b', 'c'], ' a[b][c] next ', annotate=' a[b][c] *HERE* next ')
    T(['a', 'b', 'c'], ' [a].b.c next ', annotate=' [a].b.c *HERE* next ')
    T(['a', 'b', 'c'], ' [a].b[c] next ', annotate=' [a].b[c] *HERE* next ')
    T(['a', 'b', 'c'], ' [a][b].c next ', annotate=' [a][b].c *HERE* next ')
    T(['a', 'b', 'c'], ' [a][b][c] next ', annotate=' [a][b][c] *HERE* next ')

    # aliases

    T(['r', 'r'], ' r next ', aliases=aliases, annotate=' r *HERE* next ')
    T(['r'], ' r() next ', aliases=aliases, annotate=' r *HERE* () next ')
    T(['a', 'b', 'c'], ' x next ', aliases=aliases, annotate=' x *HERE* next ')
    T(['a', 'b', 'c', 'b'], ' x. b next ', aliases=aliases,
      annotate=' x. b *HERE* next ')
    T(['a', 'b', None, 'c'], ' y next ', aliases=aliases,
      annotate=' y *HERE* next ')
    T(['a', 123, 'b', 'c'], ' z next ', aliases=aliases,
      annotate=' z *HERE* next ')
    T(['a', 'b', 'c'], ' x .d next ', aliases=aliases,
      annotate=' x *HERE* .d next ')

    # terminators

    T(['a', 'b'], ' a.b(c ', annotate=' a.b *HERE* (c ')
    T(['a', 'b'], ' a.b)c ', annotate=' a.b *HERE* )c ')
    T(['a', 'b'], ' a.b{c ', annotate=' a.b *HERE* {c ')
    T(['a', 'b'], ' a.b}c ', annotate=' a.b *HERE* }c ')
    T(['a', 'b'], ' a.b<c ', annotate=' a.b *HERE* <c ')
    T(['a', 'b'], ' a.b>c ', annotate=' a.b *HERE* >c ')
    T(['a', 'b'], ' a.b=c ', annotate=' a.b *HERE* =c ')
    T(['a', 'b'], ' a.b!c ', annotate=' a.b *HERE* !c ')
    T(['a', 'b'], ' a.b+c ', annotate=' a.b *HERE* +c ')
    T(['a', 'b'], ' a.b*c ', annotate=' a.b *HERE* *c ')
    T(['a', 'b'], ' a.b/c ', annotate=' a.b *HERE* /c ')
    T(['a', 'b'], ' a.b%c ', annotate=' a.b *HERE* %c ')
    T(['a', 'b_c'], ' a.b_c ', annotate=' a.b_c *HERE* ')
    T([None], ' []a cd', annotate=' [] *HERE* a cd')
    T([1, 'a'], ' [1].a cd', annotate=' [1].a *HERE* cd')

    # end of input

    T(['a'], 'a', annotate='a *HERE*')
    T(['a', None], 'a[]', annotate='a[] *HERE*')

    # exceptions

    T(None, '..', exception=resource_exceptions.ExpressionSyntaxError,
      annotate='. *HERE* .')
    T(None, ' a"b cd', exception=resource_exceptions.ExpressionSyntaxError,
      annotate='*HERE* a"b cd')
    T(None, ' a.', exception=resource_exceptions.ExpressionSyntaxError,
      annotate=' a. *HERE*')
    T(None, ' .a cd', exception=resource_exceptions.ExpressionSyntaxError,
      annotate=' . *HERE* a cd')
    T(None, ' a..b cd', exception=resource_exceptions.ExpressionSyntaxError,
      annotate=' a. *HERE* .b cd')
    T(None, ' a[ cd', exception=resource_exceptions.ExpressionSyntaxError,
      annotate=' a[ cd *HERE*')
    T(None, ' a] cd', exception=resource_exceptions.ExpressionSyntaxError,
      annotate=' a] *HERE* cd')
    T(None, '#type:a', exception=resource_exceptions.ExpressionSyntaxError,
      annotate='*HERE* #type:a')
class ResourceFilterTest(subtests.Base, sdk_test_base.WithLogCapture):

  _ALIASES = {
      'i': resource_lex.Lexer('integer').KeyWithAttribute(),
      'v': resource_lex.Lexer('compound.string.value').KeyWithAttribute(),
  }

  def SetUp(self):
    self.resource = None
    self.StartObjectPatch(times, 'Now', return_value=times.ParseDateTime(
        '2016-11-11T12:34:56.789-04:00'))

  def SetResource(self, resource):
    """Sets the resource for the next set of subtests."""
    self.resource = resource

  def RunSubTest(self, expression, deprecated=False):

    def _Error(resource=None):
      """Always raises ValueError for testing.

      Args:
        resource: The resource object.

      Raises:
        ValueError: Always for testing.
      """
      _ = resource
      raise ValueError('Transform function value error.')

    default_symbols = {
        'date': resource_transform.TransformDate,
        'len': lambda r, x=None: resource_transform.TransformLen(x or r),
    }
    defaults = resource_projection_parser.Parse(
        '(compound.string:alias=s, floating:alias=f)',
        symbols=default_symbols,
        aliases=self._ALIASES)
    symbols = {
        'error': _Error,  # 'error' not a magic name.
    }
    defaults = resource_projection_spec.ProjectionSpec(
        defaults=defaults, symbols=symbols)
    evaluate = resource_filter.Compile(expression, defaults=defaults).Evaluate
    if isinstance(self.resource, list):
      results = []
      for r in self.resource:
        results.append(evaluate(r))
      return results
    actual = evaluate(self.resource)
    err = self.GetErr()
    self.ClearErr()
    warning = ('WARNING: --filter : operator evaluation is changing for '
               'consistency across Google APIs.')
    if err and not deprecated:
      self.fail('Error [%s] not expected.' % err)
    elif not err and deprecated:
      self.fail('Warning [%s] expected.' % warning)
    elif err and deprecated and warning not in err:
      self.fail('Warning [%s] expected but got [%s].' % (warning, err))
    return actual

  def testResourceFilter(self):

    def T(expected, expression, deprecated=False, exception=None):
      if exception is None and expected is None:
        exception = resource_exceptions.ExpressionSyntaxError
      self.Run(expected, expression, deprecated=deprecated, depth=2,
               exception=exception)

    self.SetResource(Resource())

    # empty expression

    T(True, '')

    # integer terms

    T(False, 'integer:3')
    T(True, 'integer:2')
    T(False, 'integer:1')
    T(False, 'integer:-1')
    T(True, '-integer:3')
    T(False, '-integer:2')
    T(True, '-integer:1')
    T(True, '-integer:-1')
    T(True, 'NOT integer:3')
    T(False, 'NOT integer:2')
    T(True, 'NOT integer:1')
    T(True, 'NOT integer:-1')

    T(False, 'integer=3')
    T(True, 'integer=2')
    T(False, 'integer=1')
    T(False, 'integer=-1')
    T(True, '-integer=3')
    T(False, '-integer=2')
    T(True, '-integer=1')
    T(True, '-integer=-1')
    T(True, 'NOT integer=3')
    T(False, 'NOT integer=2')
    T(True, 'NOT integer=1')
    T(True, 'NOT integer=-1')

    T(True, 'integer<3')
    T(False, 'integer<2')
    T(False, 'integer<1')
    T(False, 'integer<-1')
    T(False, '-integer<3')
    T(True, '-integer<2')
    T(True, '-integer<1')
    T(True, '-integer<-1')
    T(False, 'NOT integer<3')
    T(True, 'NOT integer<2')
    T(True, 'NOT integer<1')
    T(True, 'NOT integer<-1')

    T(True, 'integer<=3')
    T(True, 'integer<=2')
    T(False, 'integer<=1')
    T(False, 'integer<=-1')
    T(False, '-integer<=3')
    T(False, '-integer<=2')
    T(True, '-integer<=1')
    T(True, '-integer<=-1')
    T(False, 'NOT integer<=3')
    T(False, 'NOT integer<=2')
    T(True, 'NOT integer<=1')
    T(True, 'NOT integer<=-1')

    T(False, 'integer>=3')
    T(True, 'integer>=2')
    T(True, 'integer>=1')
    T(True, 'integer>=-1')
    T(True, '-integer>=3')
    T(False, '-integer>=2')
    T(False, '-integer>=1')
    T(False, '-integer>=-1')
    T(True, 'NOT integer>=3')
    T(False, 'NOT integer>=2')
    T(False, 'NOT integer>=1')
    T(False, 'NOT integer>=-1')

    T(False, 'integer>3')
    T(False, 'integer>2')
    T(True, 'integer>1')
    T(True, 'integer>-1')
    T(True, '-integer>3')
    T(True, '-integer>2')
    T(False, '-integer>1')
    T(False, '-integer>-1')
    T(True, 'NOT integer>3')
    T(True, 'NOT integer>2')
    T(False, 'NOT integer>1')
    T(False, 'NOT integer>-1')

    T(True, 'integer!=3')
    T(False, 'integer!=2')
    T(True, 'integer!=1')
    T(True, 'integer!=-1')
    T(False, '-integer!=3')
    T(True, '-integer!=2')
    T(False, '-integer!=1')
    T(False, '-integer!=-1')
    T(False, 'NOT integer!=3')
    T(True, 'NOT integer!=2')
    T(False, 'NOT integer!=1')
    T(False, 'NOT integer!=-1')

    # numeric operand mismatches

    T(False, 'string > 1.23')
    T(True, 'integer > 1.23')

    # Boolean terms

    T(False, 'none:0')
    T(False, 'none:False')
    T(False, 'none:false')
    T(False, 'none:1')
    T(False, 'none:True')
    T(False, 'none:true')

    T(True, 'false:0')
    T(True, 'false:False')
    T(True, 'false:false')
    T(False, 'false:1')
    T(False, 'false:True')
    T(False, 'false:true')

    T(False, 'true:0')
    T(False, 'true:False')
    T(False, 'true:false')
    T(True, 'true:1')
    T(True, 'true:True')
    T(True, 'true:true')

    T(False, 'none=0')
    T(False, 'none=False')
    T(False, 'none=false')
    T(False, 'none=1')
    T(False, 'none=True')
    T(False, 'none=true')

    T(True, 'false=0')
    T(True, 'false=False')
    T(True, 'false=false')
    T(False, 'false=1')
    T(False, 'false=True')
    T(False, 'false=true')

    T(False, 'true=0')
    T(False, 'true=False')
    T(False, 'true=false')
    T(True, 'true=1')
    T(True, 'true=True')
    T(True, 'true=true')

    # case sensitive equality

    T(False, 'lower=ing')
    T(False, 'lower=Str')
    T(True, 'lower=string')
    T(True, 'lower=String')
    T(True, 'lower=STRING')

    T(False, 'mixed=ing')
    T(False, 'mixed=Str')
    T(True, 'mixed=String')
    T(True, 'mixed=StrIng')
    T(True, 'mixed=STRING')

    T(False, 'upper=ing')
    T(False, 'upper=Str')
    T(True, 'upper=string')
    T(True, 'upper=String')
    T(True, 'upper=STRING')

    T(False, 'undefined=String')
    T(False, 'undefined=STRING')
    T(False, 'undefined=string')
    T(False, 'undefined=Str')
    T(False, 'undefined=ing')

    # case insensitive : string match

    T(True, 'lower:String')
    T(True, 'lower:STRING')
    T(True, 'lower:string')
    T(True, 'lower:Str', deprecated=True)
    T(True, 'lower:ing', deprecated=True)

    T(True, 'mixed:STRING')
    T(True, 'mixed:Str', deprecated=True)
    T(True, 'mixed:String')
    T(True, 'mixed:ing', deprecated=True)
    T(True, 'mixed:string')

    T(True, 'upper:STRING')
    T(True, 'upper:Str', deprecated=True)
    T(True, 'upper:String')
    T(True, 'upper:ing', deprecated=True)
    T(True, 'upper:string')

    T(False, 'undefined:STRING')
    T(False, 'undefined:Str')
    T(False, 'undefined:String')
    T(False, 'undefined:ing')
    T(False, 'undefined:string')

    # case insensitive : string comma separated list match

    T(True, 'lower:(string, error)')
    T(True, 'lower:(String, Error)')
    T(False, 'lower:(no, match)')
    T(False, 'lower:(No, Match)')

    T(True, 'mixed:(string, error)')
    T(True, 'mixed:(String, Error)')
    T(False, 'mixed:(no, match)')
    T(False, 'mixed:(No, Match)')

    T(True, 'upper:(string, error)')
    T(True, 'upper:(String, Error)')
    T(False, 'upper:(no, match)')
    T(False, 'upper:(No, Match)')

    T(False, 'undefined:(string, error)')
    T(False, 'undefined:(String, Error)')
    T(False, 'undefined:(no, match)')
    T(False, 'undefined:(No, Match)')

    # case insensitive : string space separated list match

    T(True, 'lower:(string error)')
    T(True, 'lower:(String Error)')
    T(False, 'lower:(no match)')
    T(False, 'lower:(No Match)')

    T(True, 'mixed:(string error)')
    T(True, 'mixed:(String Error)')
    T(False, 'mixed:(no match)')
    T(False, 'mixed:(No Match)')

    T(True, 'upper:(string error)')
    T(True, 'upper:(String Error)')
    T(False, 'upper:(no match)')
    T(False, 'upper:(No Match)')

    T(False, 'undefined:(string error)')
    T(False, 'undefined:(String Error)')
    T(False, 'undefined:(no match)')
    T(False, 'undefined:(No Match)')

    # case insensitive : string OR separated list match

    T(True, 'lower:(string OR error)')
    T(True, 'lower:(String OR Error)')
    T(False, 'lower:(no OR match)')
    T(False, 'lower:(No OR Match)')

    T(True, 'mixed:(string OR error)')
    T(True, 'mixed:(String OR Error)')
    T(False, 'mixed:(no OR match)')
    T(False, 'mixed:(No OR Match)')

    T(True, 'upper:(string OR error)')
    T(True, 'upper:(String OR Error)')
    T(False, 'upper:(no OR match)')
    T(False, 'upper:(No OR Match)')

    T(False, 'logical:(string OR error)')
    T(False, 'logical:(String OR Error)')
    T(True, 'logical:(abc OR XYZ)')
    T(True, 'logical:(ABC OR xyz)')
    T(True, 'logical:(a* OR *Z)')
    T(False, 'logical:(aaa OR X*)', deprecated=True)
    T(True, 'logical:(aaa OR *Z)', deprecated=True)

    T(False, 'undefined:(string OR error)')
    T(False, 'undefined:(String OR Error)')
    T(False, 'undefined:(no OR match)')
    T(False, 'undefined:(No OR Match)')

    # anchored prefix/suffix case insensitive : string match

    T(True, 'lower:*')
    T(True, 'lower:*ing', deprecated=True)
    T(True, 'lower:S*ing', deprecated=True)
    T(True, 'lower:STR*ING', deprecated=True)
    T(True, 'lower:Str*ing', deprecated=True)
    T(True, 'lower:s*g', deprecated=True)
    T(True, 'lower:str*')

    T(True, 'mixed:*')
    T(True, 'mixed:*ing', deprecated=True)
    T(True, 'mixed:S*ing', deprecated=True)
    T(True, 'mixed:STR*ING', deprecated=True)
    T(True, 'mixed:Str*ing', deprecated=True)
    T(True, 'mixed:s*g', deprecated=True)
    T(True, 'mixed:str*')

    T(True, 'upper:*')
    T(True, 'upper:*ing', deprecated=True)
    T(True, 'upper:S*ing', deprecated=True)
    T(True, 'upper:STR*ING', deprecated=True)
    T(True, 'upper:Str*ing', deprecated=True)
    T(True, 'upper:s*g', deprecated=True)
    T(True, 'upper:str*')

    T(False, 'undefined:*')
    T(False, 'undefined:*ing')
    T(False, 'undefined:STR*ING')
    T(False, 'undefined:Str*ing')
    T(False, 'undefined:s*g')
    T(False, 'undefined:str*')

    # _Has() docstring examples

    T(True, 'subject:abc*xyz', deprecated=True)
    T(True, 'subject:abc*')
    T(True, 'subject:abc', deprecated=True)
    T(False, 'subject:*abc')
    T(False, 'subject:pdq*')
    T(True, 'subject:pdq', deprecated=True)
    T(False, 'subject:*pdq')
    T(True, 'subject:*')
    T(False, 'none:*')
    T(False, 'subject:xyz*')
    T(True, 'subject:xyz', deprecated=True)
    T(True, 'subject:*xyz', deprecated=True)

    # ~ regex match where ^ matches start of value, $ matches end of value

    T(True, 'lower~[a-z]')
    T(False, 'lower~[A-Z]')
    T(True, 'lower~ing')
    T(True, 'lower~ing$')
    T(True, 'lower~s.*g')
    T(True, 'lower~^s.*g')
    T(True, 'lower~^s.*g$')
    T(True, 'lower~s.*ing')
    T(True, 'lower~str')
    T(True, 'lower~^str')
    T(True, 'lower~st.*ng')
    T(True, 'lower~^st.*ng')
    T(True, 'lower~st.*ng$')
    T(True, 'lower~^st.*ng$')
    T(False, 'lower~STRING')
    T(False, 'lower~^STRING')
    T(False, 'lower~STRING$')
    T(False, 'lower~^STRING$')
    T(True, 'f~3.14')

    # !~ regex not match where ^ matches start of value, $ matches end of value

    T(False, 'lower!~[a-z]')
    T(True, 'lower!~[A-Z]')
    T(False, 'lower!~ing')
    T(False, 'lower!~ing$')
    T(False, 'lower!~s.*g')
    T(False, 'lower!~^s.*g')
    T(False, 'lower!~^s.*g$')
    T(False, 'lower!~s.*ing')
    T(False, 'lower!~str')
    T(False, 'lower!~^str')
    T(False, 'lower!~st.*ng')
    T(False, 'lower!~^st.*ng')
    T(False, 'lower!~st.*ng$')
    T(False, 'lower!~^st.*ng$')
    T(True, 'lower!~STRING')
    T(True, 'lower!~^STRING')
    T(True, 'lower!~STRING$')
    T(True, 'lower!~^STRING$')

    # "..." string operands for :

    T(True, 'lower:"STRING"')
    T(True, 'lower:"Str"', deprecated=True)
    T(True, 'lower:"Str*"')
    T(True, 'lower:"ri"', deprecated=True)
    T(True, 'lower:"rI"', deprecated=True)
    T(True, 'lower:"StrIng"')
    T(True, 'lower:"String"')
    T(True, 'lower:"ing"', deprecated=True)
    T(True, 'lower:"*ing"', deprecated=True)
    T(True, 'lower:"string"')

    T(True, 'mixed:"STRING"')
    T(True, 'mixed:"Str"', deprecated=True)
    T(True, 'mixed:"Str*"')
    T(True, 'mixed:"StrIng"')
    T(True, 'mixed:"String"')
    T(True, 'mixed:"ing"', deprecated=True)
    T(True, 'mixed:"*ing"', deprecated=True)

    T(True, 'upper:"STRING"')
    T(True, 'upper:"Str"', deprecated=True)
    T(True, 'upper:"Str*"')
    T(True, 'upper:"String"')
    T(True, 'upper:"ing"', deprecated=True)
    T(True, 'upper:"*ing"', deprecated=True)
    T(True, 'upper:"string"')

    T(False, 'undefined:"STRING"')
    T(False, 'undefined:"Str"')
    T(False, 'undefined:"Str"')
    T(False, 'undefined:"String"')
    T(False, 'undefined:"ing"')
    T(False, 'undefined:"ing"')
    T(False, 'undefined:"string"')

    T(True, 'compound.string.value:"Compound String"')
    T(True, 'compound.string.value:"Compound string"')
    T(True, 'compound.string.value:"c*g"', deprecated=True)
    T(True, 'compound.string.value:"compound string"')

    # "..." string operands for =

    T(False, 'lower="ing"')
    T(False, 'lower="str"')
    T(False, 'lower="Str"')
    T(True, 'lower="string"')
    T(True, 'lower="String"')
    T(True, 'lower="StrIng"')
    T(True, 'lower="STRING"')

    T(False, 'mixed="ing"')
    T(False, 'mixed="Ing"')
    T(False, 'mixed="Str"')
    T(True, 'mixed="String"')
    T(True, 'mixed="StrIng"')
    T(True, 'mixed="STRING"')

    T(False, 'upper="ing"')
    T(False, 'upper="Str"')
    T(True, 'upper="string"')
    T(True, 'upper="String"')
    T(True, 'upper="StrIng"')
    T(True, 'upper="STRING"')

    T(False, 'undefined="ing"')
    T(False, 'undefined="Str"')
    T(False, 'undefined="string"')
    T(False, 'undefined="String"')
    T(False, 'undefined="STRING"')

    T(True, 'compound.string.value="compound string"')
    T(True, 'compound.string.value="Compound string"')
    T(True, 'compound.string.value="Compound String"')

    # (number, string) X (list, dict) : tests

    T(True, 'compound.number.array:1')
    T(True, 'compound.number.array:3.14')
    T(False, 'compound.number.array:5')
    T(False, 'compound.number.array:abc')
    T(False, 'compound.number.array:Abc')

    T(True, 'compound.number.dictionary:1')
    T(True, 'compound.number.dictionary:3.14')
    T(False, 'compound.number.dictionary:5')
    T(True, 'compound.number.dictionary:Abc')
    T(True, 'compound.number.dictionary:abc')

    T(False, 'compound.string.array:1')
    T(False, 'compound.string.array:3.14')
    T(False, 'compound.string.array:5')
    T(True, 'compound.string.array:abc')
    T(True, 'compound.string.array:Abc')
    T(True, 'compound.string.array:ab', deprecated=True)
    T(True, 'compound.string.array:b', deprecated=True)
    T(True, 'compound.string.array:b', deprecated=True)
    T(True, 'compound.string.array:ab*')
    T(True, 'compound.string.array:*bC', deprecated=True)

    T(True, 'compound.string.dictionary:3.14')
    T(False, 'compound.string.dictionary:5')
    T(True, 'compound.string.dictionary:Abc')
    T(True, 'compound.string.dictionary:abc')
    T(True, 'compound.string.dictionary:ab*')

    # (...) set operands for :

    T(True, 'compound.string.array:(ab)', deprecated=True)
    T(True, 'compound.string.array:(ab*)')
    T(True, 'compound.string.dictionary:(ab*)')

    T(True, 'compound.number.array:(1)')
    T(True, 'compound.number.array:(aaa,1,zzz)')
    T(True, 'compound.number.array:(aaa, 1, zzz)')
    T(True, 'compound.number.array:(aaa 1 zzz)')
    T(True, 'compound.number.array:(aaa\n1\nzzz)')
    T(True, 'compound.number.array:(  aaa  1  zzz )')
    T(False, 'compound.number.array:(  aaa  5  zzz )')
    T(False, 'compound.number.array:(  aaa  zzz )')

    T(True, 'compound.number.dictionary:(1)')
    T(True, 'compound.number.dictionary:(aaa,1,zzz)')
    T(True, 'compound.number.dictionary:(aaa, 1, zzz)')
    T(True, 'compound.number.dictionary:(aaa 1 zzz)')
    T(True, 'compound.number.dictionary:(aaa\n1\nzzz)')
    T(True, 'compound.number.dictionary:(  aaa  1  zzz )')
    T(False, 'compound.number.dictionary:(  aaa  5  zzz )')
    T(False, 'compound.number.dictionary:(  aaa  zzz )')

    T(True, 'compound.string.array:(abc)')
    T(True, 'compound.string.array:(aaa,abc,zzz)')
    T(True, 'compound.string.array:(aaa, abc, zzz)')
    T(True, 'compound.string.array:(aaa abc zzz)')
    T(True, 'compound.string.array:(aaa\nabc\nzzz)')
    T(True, 'compound.string.array:(  aaa  abc  zzz )')
    T(False, 'compound.string.array:(  aaa  zzz )')

    T(True, 'compound.string.dictionary:(abc)')
    T(True, 'compound.string.dictionary:(aaa,abc,zzz)')
    T(True, 'compound.string.dictionary:(aaa, abc, zzz)')
    T(True, 'compound.string.dictionary:(aaa abc zzz)')
    T(True, 'compound.string.dictionary:(aaa\nabc\nzzz)')
    T(True, 'compound.string.dictionary:(  aaa  abc  zzz )')
    T(False, 'compound.string.dictionary:(  aaa  zzz )')

    # string X (list, dict) = tests

    T(False, 'compound.string.array=ab')
    T(False, 'compound.string.dictionary=ab')

    # (...) set operands for =

    T(False, 'compound.string.array=(ab)')
    T(False, 'compound.string.dictionary=(ab)')

    T(True, 'compound.number.array=(1)')
    T(True, 'compound.number.array=(aaa,1,zzz)')
    T(True, 'compound.number.array=(aaa, 1, zzz)')
    T(True, 'compound.number.array=(aaa 1 zzz)')
    T(True, 'compound.number.array=(aaa\n1\nzzz)')
    T(True, 'compound.number.array=(  aaa  1  zzz )')
    T(False, 'compound.number.array=(3)', deprecated=True)
    T(False, 'compound.number.array=(aaa,3,zzz)', deprecated=True)
    T(False, 'compound.number.array=(aaa, 3, zzz)', deprecated=True)
    T(False, 'compound.number.array=(aaa 3 zzz)', deprecated=True)
    T(False, 'compound.number.array=(aaa\n3\nzzz)', deprecated=True)
    T(False, 'compound.number.array=(  aaa  3  zzz )', deprecated=True)
    T(False, 'compound.number.array=(  aaa  5  zzz )')
    T(False, 'compound.number.array=(  aaa  zzz )')

    T(True, 'compound.number.dictionary=(1)')
    T(True, 'compound.number.dictionary=(aaa,1,zzz)')
    T(True, 'compound.number.dictionary=(aaa, 1, zzz)')
    T(True, 'compound.number.dictionary=(aaa 1 zzz)')
    T(True, 'compound.number.dictionary=(aaa\n1\nzzz)')
    T(True, 'compound.number.dictionary=(  aaa  1  zzz )')
    T(False, 'compound.number.dictionary=(  aaa  5  zzz )')
    T(False, 'compound.number.dictionary=(  aaa  zzz )')

    T(True, 'compound.string.array=(abc)')
    T(True, 'compound.string.array=(aaa,abc,zzz)')
    T(True, 'compound.string.array=(aaa, abc, zzz)')
    T(True, 'compound.string.array=(aaa abc zzz)')
    T(True, 'compound.string.array=(aaa\nabc\nzzz)')
    T(True, 'compound.string.array=(  aaa  abc  zzz )')
    T(False, 'compound.string.array=(ab)')
    T(False, 'compound.string.array=(aaa,ab,zzz)')
    T(False, 'compound.string.array=(aaa, ab, zzz)')
    T(False, 'compound.string.array=(aaa ab zzz)')
    T(False, 'compound.string.array=(aaa\nab\nzzz)')
    T(False, 'compound.string.array=(  aaa  ab  zzz )')
    T(False, 'compound.string.array=(  aaa  zzz )')

    T(True, 'compound.string.dictionary=(abc)')
    T(True, 'compound.string.dictionary=(aaa,abc,zzz)')
    T(True, 'compound.string.dictionary=(aaa, abc, zzz)')
    T(True, 'compound.string.dictionary=(aaa abc zzz)')
    T(True, 'compound.string.dictionary=(aaa\nabc\nzzz)')
    T(True, 'compound.string.dictionary=(  aaa  abc  zzz )')
    T(False, 'compound.string.dictionary=(ab)')
    T(False, 'compound.string.dictionary=(aaa,ab,zzz)')
    T(False, 'compound.string.dictionary=(aaa, ab, zzz)')
    T(False, 'compound.string.dictionary=(aaa ab zzz)')
    T(False, 'compound.string.dictionary=(aaa\nab\nzzz)')
    T(False, 'compound.string.dictionary=(  aaa  ab  zzz )')
    T(False, 'compound.string.dictionary=(  aaa  zzz )')

    # AND precedence over OR - see the OnePlatform comment below

    # OR precedence over adjacent conjunction

    T(False, 'integer:0 integer:0 OR integer:0')
    T(False, '(integer:0 integer:0) OR integer:0')
    T(False, 'integer:0 AND (integer:0 OR integer:0)')

    T(False, 'integer:0 integer:0 OR integer:2')
    T(True, '(integer:0 integer:0) OR integer:2')
    T(False, 'integer:0 AND (integer:0 OR integer:2)')

    T(False, 'integer:0 integer:2 OR integer:0')
    T(False, '(integer:0 integer:2) OR integer:0')
    T(False, 'integer:0 AND (integer:2 OR integer:0)')

    T(False, 'integer:0 integer:2 OR integer:2')
    T(True, '(integer:0 integer:2) OR integer:2')
    T(False, 'integer:0 AND (integer:2 OR integer:2)')

    T(False, 'integer:2 integer:0 OR integer:0')
    T(False, '(integer:2 integer:0) OR integer:0')
    T(False, 'integer:2 AND (integer:0 OR integer:0)')

    T(True, 'integer:2 integer:0 OR integer:2')
    T(True, '(integer:2 integer:0) OR integer:2')
    T(True, 'integer:2 AND (integer:0 OR integer:2)')

    T(True, 'integer:2 integer:2 OR integer:0')
    T(True, '(integer:2 integer:2) OR integer:0')
    T(True, 'integer:2 AND (integer:2 OR integer:0)')

    T(True, 'integer:2 integer:2 OR integer:2')
    T(True, '(integer:2 integer:2) OR integer:2')
    T(True, 'integer:2 AND (integer:2 OR integer:2)')

    T(False, 'integer:0 OR integer:0 integer:0')
    T(False, 'integer:0 OR (integer:0 integer:0)')
    T(False, '(integer:0 OR integer:0) AND integer:0')

    T(False, 'integer:0 OR integer:0 integer:2')
    T(False, 'integer:0 OR (integer:0 integer:2)')
    T(False, '(integer:0 OR integer:0) AND integer:2')

    T(False, 'integer:0 OR integer:2 integer:0')
    T(False, 'integer:0 OR (integer:2 integer:0)')
    T(False, '(integer:0 OR integer:2) AND integer:0')

    T(True, 'integer:0 OR integer:2 integer:2')
    T(True, 'integer:0 OR (integer:2 integer:2)')
    T(True, '(integer:0 OR integer:2) AND integer:2')

    T(False, 'integer:2 OR integer:0 integer:0')
    T(True, 'integer:2 OR (integer:0 integer:0)')
    T(False, '(integer:2 OR integer:0) AND integer:0')

    T(True, 'integer:2 OR integer:0 integer:2')
    T(True, 'integer:2 OR (integer:0 integer:2)')
    T(True, '(integer:2 OR integer:0) AND integer:2')

    T(False, 'integer:2 OR integer:2 integer:0')
    T(True, 'integer:2 OR (integer:2 integer:0)')
    T(False, '(integer:2 OR integer:2) AND integer:0')

    T(True, 'integer:2 OR integer:2 integer:2')
    T(True, 'integer:2 OR (integer:2 integer:2)')
    T(True, '(integer:2 OR integer:2) AND integer:2')

    T(True, 'integer=2 OR floating=3.14')
    T(True, 'integer=2 floating=3.14')
    T(True, 'integer=2 ( floating=3.14 )')
    T(True, '( integer=2 ) floating=3.14')
    T(True, '( integer=2 floating=3.14 )')

    # AND, NOT OR and (...) combinations

    T(True, 't:1 AND ( t:1 OR t:1 )')
    T(True, 't:1 AND ( t:1 OR t:0 )')
    T(True, 't:1 AND ( t:0 OR t:1 )')
    T(False, 't:1 AND ( t:0 OR t:0 )')
    T(False, 't:0 AND ( t:1 OR t:1 )')
    T(False, 't:0 AND ( t:1 OR t:0 )')
    T(False, 't:0 AND ( t:0 OR t:1 )')
    T(False, 't:0 AND ( t:0 OR t:0 )')

    T(True, '( t:1 OR t:1 ) AND t:1')
    T(True, '( t:0 OR t:1 ) AND t:1')
    T(False, '( t:1 OR t:1 ) AND t:0')
    T(False, '( t:0 OR t:1 ) AND t:0')
    T(True, '( t:1 OR t:0 ) AND t:1')
    T(False, '( t:0 OR t:0 ) AND t:1')
    T(False, '( t:1 OR t:0 ) AND t:0')
    T(False, '( t:0 OR t:0 ) AND t:0')

    T(False, 'NOT t:1 AND ( t:1 OR t:1 )')
    T(False, 'NOT t:1 AND ( t:1 OR t:0 )')
    T(False, 'NOT t:1 AND ( t:0 OR t:1 )')
    T(False, 'NOT t:1 AND ( t:0 OR t:0 )')
    T(True, 'NOT t:0 AND ( t:1 OR t:1 )')
    T(True, 'NOT t:0 AND ( t:1 OR t:0 )')
    T(True, 'NOT t:0 AND ( t:0 OR t:1 )')
    T(False, 'NOT t:0 AND ( t:0 OR t:0 )')

    T(False, 'NOT ( t:1 OR t:1 ) AND t:1')
    T(False, 'NOT ( t:0 OR t:1 ) AND t:1')
    T(False, 'NOT ( t:1 OR t:1 ) AND t:0')
    T(False, 'NOT ( t:0 OR t:1 ) AND t:0')
    T(False, 'NOT ( t:1 OR t:0 ) AND t:1')
    T(True, 'NOT ( t:0 OR t:0 ) AND t:1')
    T(False, 'NOT ( t:1 OR t:0 ) AND t:0')
    T(False, 'NOT ( t:0 OR t:0 ) AND t:0')

    # OnePlatform queries have non-standard OR >>> AND precedence. Cloud SDK
    # requires parentheses when AND and OR are combined to clarify user intent.

    # This group adds (...) to achieve standard AND/OR precedence.

    T(True, '(integer>0 AND floating>0) OR integer>0')
    T(True, '(integer>0 AND floating>0) OR integer<0')
    T(True, '(integer>0 AND floating<0) OR integer>0')
    T(False, '(integer>0 AND floating<0) OR integer<0')
    T(True, '(integer<0 AND floating>0) OR integer>0')
    T(False, '(integer<0 AND floating>0) OR integer<0')
    T(True, '(integer<0 AND floating<0) OR integer>0')
    T(False, '(integer<0 AND floating<0) OR integer<0')

    T(True, 'integer>0 OR (integer>0 AND floating>0)')
    T(True, 'integer<0 OR (integer>0 AND floating>0)')
    T(True, 'integer>0 OR (integer>0 AND floating<0)')
    T(False, 'integer<0 OR (integer>0 AND floating<0)')
    T(True, 'integer>0 OR (integer<0 AND floating>0)')
    T(False, 'integer<0 OR (integer<0 AND floating>0)')
    T(True, 'integer>0 OR (integer<0 AND floating<0)')
    T(False, 'integer<0 OR (integer<0 AND floating<0)')

    T(True, '(NOT integer>0 AND floating>0) OR integer>0')
    T(False, '(NOT integer>0 AND floating>0) OR integer<0')
    T(True, '(NOT integer>0 AND floating<0) OR integer>0')
    T(False, '(NOT integer>0 AND floating<0) OR integer<0')
    T(True, '(NOT integer<0 AND floating>0) OR integer>0')
    T(True, '(NOT integer<0 AND floating>0) OR integer<0')
    T(True, '(NOT integer<0 AND floating<0) OR integer>0')
    T(False, '(NOT integer<0 AND floating<0) OR integer<0')

    T(True, 'NOT integer>0 OR (integer>0 AND floating>0)')
    T(True, 'NOT integer<0 OR (integer>0 AND floating>0)')
    T(False, 'NOT integer>0 OR (integer>0 AND floating<0)')
    T(True, 'NOT integer<0 OR (integer>0 AND floating<0)')
    T(False, 'NOT integer>0 OR (integer<0 AND floating>0)')
    T(True, 'NOT integer<0 OR (integer<0 AND floating>0)')
    T(False, 'NOT integer>0 OR (integer<0 AND floating<0)')
    T(True, 'NOT integer<0 OR (integer<0 AND floating<0)')

    T(False, '(integer:0 AND integer:0) OR integer:0')
    T(True, '(integer:0 AND integer:0) OR integer:2')
    T(False, '(integer:0 AND integer:2) OR integer:0')
    T(True, '(integer:0 AND integer:2) OR integer:2')
    T(False, '(integer:2 AND integer:0) OR integer:0')

    T(True, '(integer:2 AND integer:0) OR integer:2')
    T(True, '(integer:2 AND integer:2) OR integer:0')
    T(True, '(integer:2 AND integer:2) OR integer:2')

    T(False, 'integer:0 OR (integer:0 AND integer:0)')
    T(False, 'integer:0 OR (integer:0 AND integer:2)')
    T(False, 'integer:0 OR (integer:2 AND integer:0)')

    T(True, 'integer:0 OR (integer:2 AND integer:2)')
    T(True, 'integer:2 OR (integer:0 AND integer:0)')
    T(True, 'integer:2 OR (integer:0 AND integer:2)')
    T(True, 'integer:2 OR (integer:2 AND integer:0)')
    T(True, 'integer:2 OR (integer:2 AND integer:2)')

    # This group checks the AND/OR combination detection.

    T(None, 'integer>0 AND floating>0 OR integer>0')
    T(None, 'integer>0 AND floating>0 OR integer<0')
    T(None, 'integer>0 AND floating<0 OR integer>0')
    T(None, 'integer>0 AND floating<0 OR integer<0')
    T(None, 'integer<0 AND floating>0 OR integer>0')
    T(None, 'integer<0 AND floating>0 OR integer<0')
    T(None, 'integer<0 AND floating<0 OR integer>0')
    T(None, 'integer<0 AND floating<0 OR integer<0')

    T(None, 'integer>0 OR integer>0 AND floating>0')
    T(None, 'integer<0 OR integer>0 AND floating>0')
    T(None, 'integer>0 OR integer>0 AND floating<0')
    T(None, 'integer<0 OR integer>0 AND floating<0')
    T(None, 'integer>0 OR integer<0 AND floating>0')
    T(None, 'integer<0 OR integer<0 AND floating>0')
    T(None, 'integer>0 OR integer<0 AND floating<0')
    T(None, 'integer<0 OR integer<0 AND floating<0')

    T(None, 'NOT integer>0 AND floating>0 OR integer>0')
    T(None, 'NOT integer>0 AND floating>0 OR integer<0')
    T(None, 'NOT integer>0 AND floating<0 OR integer>0')
    T(None, 'NOT integer>0 AND floating<0 OR integer<0')
    T(None, 'NOT integer<0 AND floating>0 OR integer>0')
    T(None, 'NOT integer<0 AND floating>0 OR integer<0')
    T(None, 'NOT integer<0 AND floating<0 OR integer>0')
    T(None, 'NOT integer<0 AND floating<0 OR integer<0')

    T(None, 'NOT integer>0 OR integer>0 AND floating>0')
    T(None, 'NOT integer<0 OR integer>0 AND floating>0')
    T(None, 'NOT integer>0 OR integer>0 AND floating<0')
    T(None, 'NOT integer<0 OR integer>0 AND floating<0')
    T(None, 'NOT integer>0 OR integer<0 AND floating>0')
    T(None, 'NOT integer<0 OR integer<0 AND floating>0')
    T(None, 'NOT integer>0 OR integer<0 AND floating<0')
    T(None, 'NOT integer<0 OR integer<0 AND floating<0')

    T(None, 'integer:0 AND integer:0 OR integer:0')
    T(None, 'integer:0 AND integer:0 OR integer:2')
    T(None, 'integer:0 AND integer:2 OR integer:0')
    T(None, 'integer:0 AND integer:2 OR integer:2')
    T(None, 'integer:2 AND integer:0 OR integer:0')
    T(None, 'integer:2 AND integer:0 OR integer:2')
    T(None, 'integer:2 AND integer:2 OR integer:0')
    T(None, 'integer:2 AND integer:2 OR integer:2')
    T(None, 'integer:0 OR integer:0 AND integer:0')
    T(None, 'integer:0 OR integer:0 AND integer:2')
    T(None, 'integer:0 OR integer:2 AND integer:0')
    T(None, 'integer:0 OR integer:2 AND integer:2')
    T(None, 'integer:2 OR integer:0 AND integer:0')
    T(None, 'integer:2 OR integer:0 AND integer:2')
    T(None, 'integer:2 OR integer:2 AND integer:0')
    T(None, 'integer:2 OR integer:2 AND integer:2')

    # > 1 of the same operator for off-by-1 grammar production bugs

    T(False, 'integer:1 AND integer:2 AND integer:3')
    T(True, 'integer>1 AND integer:2 AND integer<3')
    T(True, 'integer:1 OR integer:2 OR integer:3')
    T(False, 'integer<1 OR -integer:2 OR integer>3')

    # (...) nesting

    T(False, '( t:0 OR t:0 ) AND ( t:0 OR t:0 )')
    T(False, '( t:0 OR t:0 ) AND ( t:0 OR t:1 )')
    T(False, '( t:0 OR t:0 ) AND ( t:1 OR t:0 )')
    T(False, '( t:0 OR t:0 ) AND ( t:1 OR t:1 )')

    T(False, '( t:0 OR t:1 ) AND ( t:0 OR t:0 )')
    T(True, '( t:0 OR t:1 ) AND ( t:0 OR t:1 )')
    T(True, '( t:0 OR t:1 ) AND ( t:1 OR t:0 )')
    T(True, '( t:0 OR t:1 ) AND ( t:1 OR t:1 )')

    T(False, '( t:1 OR t:0 ) AND ( t:0 OR t:0 )')
    T(True, '( t:1 OR t:0 ) AND ( t:0 OR t:1 )')
    T(True, '( t:1 OR t:0 ) AND ( t:1 OR t:0 )')
    T(True, '( t:1 OR t:0 ) AND ( t:1 OR t:1 )')

    T(False, '( t:1 OR t:1 ) AND ( t:0 OR t:0 )')
    T(True, '( t:1 OR t:1 ) AND ( t:0 OR t:1 )')
    T(True, '( t:1 OR t:1 ) AND ( t:1 OR t:0 )')
    T(True, '( t:1 OR t:1 ) AND ( t:1 OR t:1 )')

    # space combinations

    T(True, 'integer:2')
    T(True, 'integer:2  ')
    T(True, 'integer:  2')
    T(True, 'integer  :2')
    T(True, '  integer:2')
    T(True, 'integer:  2  ')
    T(True, 'integer  :2  ')
    T(True, '  integer:2  ')
    T(True, 'integer  :  2')
    T(True, '  integer:  2')
    T(True, '  integer  :2')
    T(True, 'integer  :  2  ')
    T(True, '  integer:  2  ')
    T(True, ' integer :  2')
    T(True, '  integer  :  2  ')

    T(False, '-integer:2')
    T(False, '-integer:2  ')
    T(False, '-integer:  2')
    T(False, '-integer  :2')
    T(False, '-integer:  2  ')
    T(False, '-integer  :2  ')
    T(False, '-integer  :  2')
    T(False, '-integer  :  2  ')

    T(False, '  -integer:2')
    T(False, '  -integer:2  ')
    T(False, '  -integer:  2')
    T(False, '  -integer  :2')
    T(False, '  -integer:  2  ')
    T(False, '  -integer  :2  ')
    T(False, '  -integer  :  2')
    T(False, '  -integer  :  2  ')

    T(False, '-  integer:2')
    T(False, '-  integer:2  ')
    T(False, '-  integer:  2')
    T(False, '-  integer  :2')
    T(False, '-  integer:  2  ')
    T(False, '- integer :  2')
    T(False, '-  integer  :  2  ')
    T(False, '  -  integer:2')
    T(False, '  -  integer:2  ')
    T(False, '  -  integer:  2')
    T(False, '  -  integer  :2')
    T(False, '  -  integer:  2  ')
    T(False, '  - integer :  2')
    T(False, '  -  integer  :  2  ')
    T(False, '  -  integer:2')
    T(False, '  -  integer:2  ')
    T(False, '  -  integer:  2')
    T(False, '  -  integer  :2')
    T(False, '  -    integer:2')
    T(False, '  -  integer:  2  ')
    T(False, '  -  integer  :2  ')
    T(False, '  -    integer:2  ')
    T(False, '  -  integer  :  2')
    T(False, '  -    integer:  2')
    T(False, '  -    integer  :2')
    T(False, '  -  integer  :  2  ')
    T(False, '  -    integer:  2  ')
    T(False, '  -   integer :  2')
    T(False, '  -    integer  :  2  ')

    T(False, 'NOT  integer:2')
    T(False, 'NOT  integer:2  ')
    T(False, 'NOT  integer:  2')
    T(False, 'NOT  integer  :2')
    T(False, 'NOT    integer:2')
    T(False, 'NOT  integer:  2  ')
    T(False, 'NOT  integer  :2  ')
    T(False, 'NOT    integer:2  ')
    T(False, 'NOT  integer  :  2')
    T(False, 'NOT    integer:  2')
    T(False, 'NOT    integer  :2')
    T(False, 'NOT  integer  :  2  ')
    T(False, 'NOT    integer:  2  ')
    T(False, 'NOT   integer :  2')
    T(False, 'NOT    integer  :  2  ')

    T(False, '  NOT  integer:2')
    T(False, '  NOT  integer:2  ')
    T(False, '  NOT  integer:  2')
    T(False, '  NOT  integer  :2')
    T(False, '  NOT    integer:2')
    T(False, '  NOT  integer:  2  ')
    T(False, '  NOT  integer  :2  ')
    T(False, '  NOT    integer:2  ')
    T(False, '  NOT  integer  :  2')
    T(False, '  NOT    integer:  2')
    T(False, '  NOT    integer  :2')
    T(False, '  NOT  integer  :  2  ')
    T(False, '  NOT    integer:  2  ')
    T(False, '  NOT   integer :  2')
    T(False, '  NOT    integer  :  2  ')

    # key restrictions

    T(True, 'lower.len():6')
    T(True, 'lower.len()=6')
    T(False, 'lower.len()<6')
    T(None, 'integer.error()=0', exception=ValueError)

    # global function restrictions

    T(4, 'len(junk)')
    T(True, 'len(junk)=4')
    T(False, 'len')
    T(False, 'len (junk)')
    T(False, 'len OR (junk)')
    T(False, 'len (abc)')
    T(True, 'len OR (abc)')
    T(True, 'len OR abc')

    # global restrictions

    T(True, 'abc')
    T(True, 'pdq')
    T(True, 'xyz')
    T(False, 'aaa pdq zzz')
    T(True, 'aaa OR pdq OR zzz')
    T(False, 'aaa zzz')

    T(True, '(abc)')
    T(True, '(pdq)')
    T(True, 'abc (pdq)')
    T(True, '(abc) pdq')
    T(True, '(abc) (pdq)')

    # quotes

    T(True, 'lower="string"')
    T(True, 'double=a\\"" "\\"z')
    T(True, 'double=\'a" "z\'')
    T(True, "single=a\\'' '\\'z")
    T(True, "single=\"a' 'z\"")

    T(True, 'v~""')
    T(True, 'v~" "')

    T(True, 'v~"" OR i:1')
    T(True, 'v~"" OR i:2')
    T(False, 'v~"" AND i:1')
    T(True, 'v~"" AND i:2')
    T(True, 'v~"" NOT i:1')
    T(False, 'v~"" NOT i:2')

    T(True, 'i:1 OR v~""')
    T(True, 'i:2 OR v~""')
    T(False, 'i:1 AND v~""')
    T(True, 'i:2 AND v~""')
    T(True, 'NOT i:1 v~""')
    T(False, 'NOT i:2 v~""')

    T(True, 'v~" " OR i:1')
    T(True, 'v~" " OR i:2')
    T(False, 'v~" " AND i:1')
    T(True, 'v~" " AND i:2')
    T(True, 'v~" " NOT i:1')
    T(False, 'v~" " NOT i:2')

    # aliases

    T(True, 'i=2 OR f=3.14')
    T(True, 'i=2 f=3.14')
    T(True, 'i=2 ( f=3.14 )')
    T(True, '( i=2 ) f=3.14')
    T(True, '( i=2 f=3.14 )')

    T(False, 's.value="c*g"')
    T(True, 's.value:"c*g"', deprecated=True)
    T(False, 's.value="C*g"')
    T(True, 's.value:"C*g"', deprecated=True)
    T(True, 's.value="compound string"')
    T(True, 's.value:"compound string"')
    T(True, 's.value="Compound string"')
    T(True, 's.value:"Compound string"')
    T(True, 's.value="Compound String"')
    T(True, 's.value:"Compound String"')

    T(False, 'v="c*g"')
    T(True, 'v:"c*g"', deprecated=True)
    T(False, 'v="C*g"')
    T(True, 'v:"C*g"', deprecated=True)
    T(True, 'v="compound string"')
    T(True, 'v:"compound string"')
    T(True, 'v="Compound string"')
    T(True, 'v:"Compound string"')
    T(True, 'v="Compound String"')
    T(True, 'v:"Compound String"')

    # datetime

    T(True, 'timestamp.date(%Y)=2016')
    T(True, 'timestamp>"January 2016" timestamp<2016-09-01')
    T(True, 'timestamp<-p1m')
    T(False, 'timestamp<-p1y')

    # bad key OK global restriction

    T(True, '.')
    T(False, '..')
    T(False, '...')
    T(False, '.foo')
    T(False, 'foo.')
    T(False, 'foo..bar')

    # syntax errors

    T(None, 'AND')
    T(None, 'NOT')
    T(None, 'OR')
    T(None, '..len()')
    T(None, '.foo.len()')
    T(None, 'foo..len()')
    T(None, 'foo..bar.len()')
    T(None, 'foo.len(')
    T(None, 'foo.len)')
    T(None, '(')
    T(None, ')')
    T(None, ':')
    T(None, 'foo:')
    T(None, ':bar')
    T(None, ':.foo')
    T(None, 'foo> (')
    T(None, 'foo>bar (')
    T(None, '(')
    T(None, 'foo )')
    T(None, 'foo> )')
    T(None, 'foo>bar )')
    T(None, ')')
    T(None, '"')
    T(None, "'")
    T(None, 'foo:"bar')
    T(None, 'foo<(bar)')
    T(None, 'foo>(bar, baz)')

    T(None, 'a!b')
    T(None, 'a!!b')
    T(None, 'a!:b')
    T(None, 'a!<b')
    T(None, 'a!>b')
    T(None, 'a:!b')
    T(None, 'a::b')
    T(None, 'a:=b')
    T(None, 'a:<b')
    T(None, 'a:>b')
    T(None, 'a=!b')
    T(None, 'a=:b')
    T(None, 'a==b')
    T(None, 'a=<b')
    T(None, 'a=>b')
    T(None, 'a<!b')
    T(None, 'a<:b')
    T(None, 'a<<b')
    T(None, 'a<>b')
    T(None, 'a>!b')
    T(None, 'a>:b')
    T(None, 'a><b')
    T(None, 'a>>b')

    T(None, 'a:"b')
    T(None, "a:'b")
    T(None, 'a:\\"b"')

    T(None, '>1')
    T(None, 'NOT >1')
    T(None, 'a.foo()')

    T(None, 'a:b OR')
    T(None, 'a:b AND')
    T(None, 'a:b NOT')

    T(None, 'a: OR b:1')
    T(None, 'a: AND b:1')
    T(None, 'a: NOT b:1')

    T(None, 'a:b (')
    T(None, 'a:b )')
    T(None, 'a:b ()')

    T(None, 'v:"c*d s*g"')

    # ~ regex errors.

    T(None, 'v~*')
    T(None, 'v~)')

    # OnePlatform examples.

    self.SetResource({
        'foo': 'bar',
        'obj': {
            'x': 10,
            'y': 20,
        },
        'ids': [2, 3, 4],
        'required': True,
        'ptr': None,
        'members': [
            {
                'name': 'Joe',
                'age': 40,
            },
            {
                'name': 'Jane',
                'age': 50,
            },
        ],
    })

    T(True, 'foo = bar')
    T(True, 'obj.x = 10')
    T(True, 'required = true')
    T(True, 'ptr = null')
    T(True, 'members.name = Jane')
    T(True, 'members.age > 40')

    # Repeated value examples.

    self.SetResource([
        {
            'name': 'instance-1',
            'networkInterfaces': [
                {
                    'accessConfigs': [
                        {
                            'natIP': '23.251.133.75'
                        },
                        {
                            'natIP': '23.251.133.76'
                        },
                        {
                            'foo': 'bar'
                        }
                    ],
                    'networkIP': '10.0.0.1'
                },
                {
                    'accessConfigs': [
                        {
                            'natIP': '23.251.133.74'
                        }
                    ],
                    'networkIP': '10.0.0.2'
                },
                {
                    'bar': 'foo'
                }
            ]
        },
        {
            'name': 'instance-2',
            'networkInterfaces': [
                {
                    'accessConfigs': [
                        {
                            'natIP': '23.251.133.74'
                        }
                    ],
                    'networkIP': '10.0.0.2'
                },
                {
                    'foo': 'bar'
                }
            ]
        },
        {
            'name': 'instance-3',
            'networkInterfaces': [
                {
                    'accessConfigs': [
                        {
                            'natIP': '23.251.133.76'
                        }
                    ],
                    'networkIP': '10.0.0.3'
                }
            ]
        }
    ])

    T([False, False, False], 'networkInterfaces.len() > 3')
    T([False, False, False], 'networkInterfaces[3]:*')
    T([True, False, False], 'networkInterfaces.len() > 2')
    T([True, False, False], 'networkInterfaces[2]:*')
    T([True, True, False], 'networkInterfaces.len() > 1')
    T([True, True, False], 'networkInterfaces[1]:*')
    T([True, True, True], 'networkInterfaces.len() > 0')
    T([True, True, True], 'networkInterfaces[0]:*')