blob: 730bfdbc610762384c7ea5954abd5922b5d91cd3 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2022 Project CHIP Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import unittest
import yaml
import logging
from typing import List
from dataclasses import dataclass, field
try:
from idl.matter_idl_parser import CreateParser
except:
import sys
sys.path.append(os.path.abspath(
os.path.join(os.path.dirname(__file__), '..')))
from idl.matter_idl_parser import CreateParser
from idl.matter_idl_types import Idl
from idl.generators.java import JavaGenerator
from idl.generators.bridge import BridgeGenerator
from idl.generators.cpp.application import CppApplicationGenerator
from idl.generators import GeneratorStorage
TESTS_DIR = os.path.join(os.path.dirname(__file__), "tests")
REGENERATE_GOLDEN_IMAGES = False
@dataclass
class ExpectedOutput:
file_name: str
golden_path: str
@dataclass
class GeneratorTestCase:
input_idl: str
outputs: List[ExpectedOutput] = field(default_factory=list)
def add_outputs(self, yaml_outputs_dict):
for file_name, golden_path in yaml_outputs_dict.items():
self.outputs.append(ExpectedOutput(
file_name=file_name, golden_path=golden_path))
class TestCaseStorage(GeneratorStorage):
def __init__(self, test_case: GeneratorTestCase, checker: unittest.TestCase):
super().__init__()
self.test_case = test_case
self.checker = checker
self.checked_files = set()
def get_existing_data_path(self, relative_path: str):
for expected in self.test_case.outputs:
if expected.file_name == relative_path:
return os.path.join(TESTS_DIR, expected.golden_path)
self.checker.fail("Expected output %s not found" % relative_path)
return None
def get_existing_data(self, relative_path: str):
self.checked_files.add(relative_path)
path = self.get_existing_data_path(relative_path)
if path:
with open(path, 'rt') as golden:
return golden.read()
# This will attempt a new write, causing a unit test failure
self.checker.fail("Expected output %s not found" % relative_path)
return None
def write_new_data(self, relative_path: str, content: str):
if REGENERATE_GOLDEN_IMAGES:
print("RE-GENERATING %r" % relative_path)
# Expect writing only on regeneration
with open(self.get_existing_data_path(relative_path), 'wt') as golden:
golden.write(content)
return
# This is a unit test failure: we do NOT expect
# to write any new data
# This will display actual diffs in the output files
self.checker.assertEqual(
self.get_existing_data(relative_path), content, "Content of %s" % relative_path)
# Even if no diff, to be build system friendly, we do NOT expect any
# actual data writes.
raise AssertionError("Unexpected write to %s" % relative_path)
@dataclass
class GeneratorTest:
generator_name: str
test_cases: List[GeneratorTestCase] = field(default_factory=list)
def add_test_cases(self, yaml_test_case_dict):
for idl_path, outputs in yaml_test_case_dict.items():
test_case = GeneratorTestCase(input_idl=idl_path)
test_case.add_outputs(outputs)
self.test_cases.append(test_case)
def _create_generator(self, storage: GeneratorStorage, idl: Idl):
if self.generator_name.lower() == 'java':
return JavaGenerator(storage, idl)
if self.generator_name.lower() == 'bridge':
return BridgeGenerator(storage, idl)
if self.generator_name.lower() == 'cpp-app':
return CppApplicationGenerator(storage, idl)
else:
raise Exception("Unknown generator for testing: %s",
self.generator_name.lower())
def run_test_cases(self, checker: unittest.TestCase):
for test in self.test_cases:
with checker.subTest(idl=test.input_idl):
storage = TestCaseStorage(test, checker)
with open(os.path.join(TESTS_DIR, test.input_idl), 'rt') as stream:
idl = CreateParser().parse(stream.read())
generator = self._create_generator(storage, idl)
generator.render(dry_run=False)
checker.assertEqual(storage.checked_files, set(
map(lambda x: x.file_name, test.outputs)))
def build_tests(yaml_data) -> List[GeneratorTest]:
"""
Transforms the YAML dictonary (Dict[str, Dict[str, Dict[str,str]]]) into
a generator test structure.
"""
result = []
for key, test_cases in yaml_data.items():
generator = GeneratorTest(generator_name=key)
generator.add_test_cases(test_cases)
result.append(generator)
return result
class TestGenerators(unittest.TestCase):
def test_generators(self):
with open(os.path.join(TESTS_DIR, "available_tests.yaml"), 'rt') as stream:
yaml_data = yaml.safe_load(stream)
for test in build_tests(yaml_data):
with self.subTest(generator=test.generator_name):
test.run_test_cases(self)
if __name__ == '__main__':
if 'IDL_GOLDEN_REGENERATE' in os.environ:
# run with `IDL_GOLDEN_REGENERATE=1` to cause a regeneration of test
# data. Then one can use `git diff` to see if the deltas make sense
REGENERATE_GOLDEN_IMAGES = True
unittest.main()