Skip to content

Commit

Permalink
Update tests and documentation #56
Browse files Browse the repository at this point in the history
    * Return license validation results in ExpressionInfo object

Signed-off-by: Jono Yang <[email protected]>
  • Loading branch information
JonoYang committed Jun 4, 2021
1 parent 97d1fe6 commit 4521921
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 16 deletions.
59 changes: 43 additions & 16 deletions src/license_expression/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,46 +603,73 @@ def simple_tokenizer(self, expression):
sym = LicenseSymbol(key=sym_or_op)
yield Token(start, end, sym_or_op, sym)

def validate(self, expression, strict=False, **kwargs):
data = {
'normalized_license_expression': '',
'errors': [],
'valid_symbols': [],
'invalid_symbols': [],
'exception_symbols': [],
}
def validate(self, expression, strict=True, **kwargs):
"""
Return a ExpressionInfo object that contains information about
`expression` by parsing `expression` using Licensing.parse()
The ExpressionInfo class has the following fields:
- normalized_license_expression: str
- errors: list
- valid_symbols: list
- invalid_symbols: list
- exception_symbols: list
If `expression` is valid, then
`ExpressionInfo.normalized_license_expression` is set, along with a list
of valid license symbols in `valid_symbols` and `exception_symbols`.
If an error was encountered when validating `expression`,
`ExpressionInfo.errors` will be populated with strings containing the
error message that has occured. If an error has occured due to invalid
license symbols, the offending symbols will be present in
`ExpressionInfo.invalid_symbols`
If `strict` is True, additional exceptions will be raised if in a "WITH"
expression such as "XXX with ZZZ" if the XXX symbol has `is_exception`
set to True or the YYY symbol has `is_exception` set to False. This
checks that symbols are used strictly as constructed.
"""
class ExpressionInfo:
normalized_license_expression = ''
errors = []
valid_symbols = []
invalid_symbols = []
exception_symbols = []

data = ExpressionInfo()

# Check `expression` type
try:
self.parse(expression)
except ExpressionError as e:
data['errors'].append(str(e))
data.errors.append(str(e))
return data

# Check `expression` syntax
try:
self.parse(expression, strict=strict)
except ExpressionParseError as e:
data['errors'].append(str(e))
data['invalid_symbols'].append(e.token_string)
data.errors.append(str(e))
data.invalid_symbols.append(e.token_string)
return data

# Check `expression` keys
try:
parsed_expression = self.parse(expression, strict=strict, validate=True)
except ExpressionError as e:
error_message = str(e)
data['errors'].append(error_message)
data.errors.append(error_message)
if 'Unknown license key' in error_message:
unknown_keys = self.unknown_license_keys(expression)
data['invalid_symbols'].extend(unknown_keys)
data.invalid_symbols.extend(unknown_keys)
return data

# If we have not hit an exception, load `data` and return it
symbols = list(parsed_expression.symbols)
data['normalized_license_expression'] = parsed_expression.render()
data['valid_symbols'] = [s.render() for s in symbols]
data['exception_symbols'] = [s.render() for s in symbols if isinstance(s, LicenseWithExceptionSymbol) or s.is_exception]
data.normalized_license_expression = parsed_expression.render()
data.valid_symbols = [s.render() for s in symbols]
data.exception_symbols = [s.render() for s in symbols if isinstance(s, LicenseWithExceptionSymbol) or s.is_exception]
return data


Expand Down
59 changes: 59 additions & 0 deletions tests/test_license_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -2165,3 +2165,62 @@ def test_tokenize_or_or(self):
]

assert expected == results


class LicensingValidateTest(TestCase):
licensing = Licensing(
[
LicenseSymbol(key='GPL-2.0-or-later', is_exception=False),
LicenseSymbol(key='MIT', is_exception=False),
LicenseSymbol(key='Apache-2.0', is_exception=False),
LicenseSymbol(key='WxWindows-exception-3.1', is_exception=True),
]
)

def test_validate_simple(self):
result = self.licensing.validate('GPL-2.0-or-later AND MIT')
assert 'GPL-2.0-or-later AND MIT' == result.normalized_license_expression
assert [] == result.errors
assert sorted(['MIT', 'GPL-2.0-or-later']) == sorted(result.valid_symbols)
assert [] == result.invalid_symbols
assert [] == result.exception_symbols

def test_validation_invalid_license_key(self):
result = self.licensing.validate('cool-license')
assert '' == result.normalized_license_expression
assert ['Unknown license key(s): cool-license'] == result.errors
assert [] == result.valid_symbols
assert ['cool-license'] == result.invalid_symbols
assert [] == result.exception_symbols

def test_validate_exception(self):
result = self.licensing.validate('GPL-2.0-or-later WITH WxWindows-exception-3.1')
assert 'GPL-2.0-or-later WITH WxWindows-exception-3.1' == result.normalized_license_expression
assert [] == result.errors
assert ['GPL-2.0-or-later WITH WxWindows-exception-3.1'] == result.valid_symbols
assert [] == result.invalid_symbols
assert ['GPL-2.0-or-later WITH WxWindows-exception-3.1'] == result.exception_symbols

def test_validation_exception_with_choice(self):
result = self.licensing.validate('GPL-2.0-or-later WITH WxWindows-exception-3.1 OR MIT')
assert 'GPL-2.0-or-later WITH WxWindows-exception-3.1 OR MIT' == result.normalized_license_expression
assert [] == result.errors
assert sorted(['GPL-2.0-or-later WITH WxWindows-exception-3.1', 'MIT']) == sorted(result.valid_symbols)
assert [] == result.invalid_symbols
assert ['GPL-2.0-or-later WITH WxWindows-exception-3.1'] == result.exception_symbols

def test_validation_bad_syntax(self):
result = self.licensing.validate('Apache-2.0 + MIT')
assert '' == result.normalized_license_expression
assert ['Invalid symbols sequence such as (A B) for token: "+" at position: 11'] == result.errors
assert [] == result.valid_symbols
assert [] == result.invalid_symbols
assert [] == result.exception_symbols

def test_validation_invalid_license_exception(self):
result = self.licensing.validate('Apache-2.0 WITH MIT')
assert '' == result.normalized_license_expression
assert ["A plain license symbol cannot be used as an exception in a \"WITH symbol\" statement. for token: \"MIT\" at position: 16"] == result.errors
assert [] == result.valid_symbols
assert ['MIT'] == result.invalid_symbols
assert [] == result.exception_symbols

0 comments on commit 4521921

Please sign in to comment.