Refactor WASM binding directory structure
* All tests moved into wasm/codegen/tests folder * Merged wasm/codegen/helpers/ into wasm/codegen/generators/ PiperOrigin-RevId: 829471173 Change-Id: I2dbc5d9351771817ec260c7ddf87c31e66a9eabe
This commit is contained in:
committed by
Copybara-Service
parent
44220fcc51
commit
4e46db8903
@@ -0,0 +1,152 @@
|
||||
# Copyright 2025 DeepMind Technologies Limited
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Tests to ensure that all Mujoco functions and structs are correctly handled.
|
||||
|
||||
This file contains tests that verify:
|
||||
- All functions defined in Mujoco's introspect module are either bound in the
|
||||
generated bindings.cc file or explicitly excluded in constants.py.
|
||||
- All structs defined in Mujoco's introspect module are either bound in the
|
||||
generated bindings.cc file or explicitly skipped in SKIPPED_STRUCTS in
|
||||
constants.py.
|
||||
|
||||
These tests help maintain the integrity of the generated WASM bindings by
|
||||
ensuring that no functions or structs are accidentally missed or incorrectly
|
||||
handled during the code generation process.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from absl.testing import absltest
|
||||
from introspect import functions as introspect_functions
|
||||
from introspect import structs as introspect_structs
|
||||
|
||||
from wasm.codegen.generators import common
|
||||
from wasm.codegen.generators import constants
|
||||
from wasm.codegen.generators import functions
|
||||
|
||||
|
||||
def _get_resource_content(file_path: str) -> str:
|
||||
"""Reads resource file content using resources.GetResource."""
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
print(f'Warning: Resource {file_path} not found.')
|
||||
return ''
|
||||
except IOError as e:
|
||||
print(f'Error reading resource {file_path}: {e}')
|
||||
return ''
|
||||
|
||||
|
||||
def _get_bound_functions_from_cc() -> set[str]:
|
||||
"""Reads bindings.cc and extracts the names of bound functions."""
|
||||
content = _get_resource_content(
|
||||
Path(__file__).parent / 'generated/bindings.cc'
|
||||
)
|
||||
if not content:
|
||||
return set()
|
||||
|
||||
bound_functions = set()
|
||||
# Find all strings within function("...") calls.
|
||||
matches = re.findall(r'function\("([^"]+)"', content)
|
||||
bound_functions.update(matches)
|
||||
return bound_functions
|
||||
|
||||
|
||||
def _get_bound_structs_from_cc() -> set[str]:
|
||||
"""Reads bindings.cc and extracts the names of bound structs."""
|
||||
content = _get_resource_content(
|
||||
Path(__file__).parent / 'generated/bindings.cc'
|
||||
)
|
||||
if not content:
|
||||
return set()
|
||||
|
||||
bound_structs = set()
|
||||
# Find all strings within class_<...>("...") calls.
|
||||
matches = re.findall(r'class_<[^>]+>\("([^"]+)"\)', content)
|
||||
bound_structs.update(matches)
|
||||
return bound_structs
|
||||
|
||||
|
||||
class BindingCoverageTest(absltest.TestCase):
|
||||
|
||||
def test_function_coverage(self):
|
||||
"""Asserts that each function is either excluded or bound."""
|
||||
all_functions = set(introspect_functions.FUNCTIONS.keys())
|
||||
excluded_functions = {
|
||||
name
|
||||
for name in all_functions
|
||||
if functions.is_excluded_function_name(name)
|
||||
}
|
||||
bound_functions = _get_bound_functions_from_cc()
|
||||
|
||||
missing_functions = []
|
||||
for func_name in all_functions:
|
||||
if (
|
||||
func_name not in excluded_functions
|
||||
and func_name not in bound_functions
|
||||
):
|
||||
missing_functions.append(func_name)
|
||||
|
||||
if missing_functions:
|
||||
error_message = (
|
||||
f"""The following functions from functions.py are neither excluded in
|
||||
constants.py nor bound in bindings.cc:
|
||||
|
||||
{", ".join(sorted(missing_functions))}
|
||||
|
||||
Please either add them to a exclusion list in
|
||||
constants.py or create a binding in bindings.cc."""
|
||||
)
|
||||
self.fail(error_message)
|
||||
|
||||
def test_struct_coverage(self):
|
||||
"""Asserts that each struct is either not bound or bound in structs.cc."""
|
||||
bound_structs = _get_bound_structs_from_cc()
|
||||
all_structs = {
|
||||
common.uppercase_first_letter(struct_name)
|
||||
for struct_name in introspect_structs.STRUCTS.keys()
|
||||
}
|
||||
skipped_structs = {
|
||||
common.uppercase_first_letter(struct_name)
|
||||
for struct_name in constants.SKIPPED_STRUCTS
|
||||
}
|
||||
missing_structs = []
|
||||
for struct_name in all_structs:
|
||||
if (
|
||||
struct_name not in skipped_structs
|
||||
and struct_name not in bound_structs
|
||||
):
|
||||
missing_structs.append(struct_name)
|
||||
error_messages = []
|
||||
|
||||
if missing_structs:
|
||||
error_messages.append(
|
||||
f"""The following structs are defined in structs.py but are neither
|
||||
bound in bindings.cc nor listed in SKIPPED_STRUCTS:
|
||||
|
||||
{", ".join(sorted(missing_structs))}
|
||||
|
||||
Please either add them to SKIPPED_STRUCTS or create its binding
|
||||
in bindings.cc."""
|
||||
)
|
||||
|
||||
if error_messages:
|
||||
self.fail('\n\n'.join(error_messages))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
absltest.main()
|
||||
Reference in New Issue
Block a user