diff options
Diffstat (limited to 'toolkit/components/extensions/webidl-api/test')
8 files changed, 631 insertions, 0 deletions
diff --git a/toolkit/components/extensions/webidl-api/test/README.md b/toolkit/components/extensions/webidl-api/test/README.md new file mode 100644 index 0000000000..14baae0d82 --- /dev/null +++ b/toolkit/components/extensions/webidl-api/test/README.md @@ -0,0 +1,60 @@ +pytest test coverage for the GenerateWebIDLBindings.py script +============================================================= + +This directory contains tests for the GenerateWebIDLBindings.py script, +which is used to parse the WebExtensions APIs schema files and generate +the corresponding WebIDL definitions. + +See ["WebIDL WebExtensions API Bindings" section from the Firefox Developer documentation](https://firefox-source-docs.mozilla.org/toolkit/components/extensions/webextensions/webidl_bindings.html) +for more details about how the script is used, this README covers only how +this test suite works. + +Run tests +--------- + +The tests part of this test suite can be executed locally using the following `mach` command: + +``` +mach python-test toolkit/components/extensions/webidl-api/test +``` + +Write a new test file +--------------------- + +To add a new test file to this test suite: +- create a new python script file named as `test_....py` +- add the test file to the `python.ini` manifest +- In the new test file make sure to include: + - copyright notes as the other test file in this directory + - import the helper module and call its `setup()` method (`setup` makes sure to add + the directory where the target script is in the python library paths and the + `helpers` module does also enable the code coverage if the environment variable + is detected): + ``` + import helpers # Import test helpers module. + ... + + helpers.setup() + ``` + - don't forget to call `mozunit.main` at the end of the test file: + ``` + if __name__ == "__main__": + mozunit.main() + ``` + - add new test cases by defining new functions named as `test_...`, + its parameter are the names of the pytest fixture functions to + be passed to the test case: + ``` + def test_something(base_schema, write_jsonschema_fixtures): + ... + ``` +Create new test fixtures +------------------------ + +All the test fixture used by this set of tests are defined in `conftest.py` +and decorated with `@pytest.fixture`. + +See the pytest documentation for more details about how the pytest fixture works: +- https://docs.pytest.org/en/latest/explanation/fixtures.html +- https://docs.pytest.org/en/latest/how-to/fixtures.html +- https://docs.pytest.org/en/latest/reference/fixtures.html diff --git a/toolkit/components/extensions/webidl-api/test/conftest.py b/toolkit/components/extensions/webidl-api/test/conftest.py new file mode 100644 index 0000000000..1e41ed0690 --- /dev/null +++ b/toolkit/components/extensions/webidl-api/test/conftest.py @@ -0,0 +1,39 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os + +import pytest + + +@pytest.fixture +def base_schema(): + def inner(): + return { + "namespace": "testAPIName", + "permissions": [], + "types": [], + "functions": [], + "events": [], + } + + return inner + + +@pytest.fixture +def write_jsonschema_fixtures(tmpdir): + """Write test schema data into per-testcase (in tmpdir or the given directory)""" + + def inner(jsonschema_fixtures, targetdir=None): + assert jsonschema_fixtures + if targetdir is None: + targetdir = tmpdir + for filename, filecontent in jsonschema_fixtures.items(): + assert isinstance(filename, str) and filename + assert isinstance(filecontent, str) and filecontent + with open(os.path.join(targetdir, filename), "w") as jsonfile: + jsonfile.write(filecontent) + return targetdir + + return inner diff --git a/toolkit/components/extensions/webidl-api/test/helpers.py b/toolkit/components/extensions/webidl-api/test/helpers.py new file mode 100644 index 0000000000..e2ebec7103 --- /dev/null +++ b/toolkit/components/extensions/webidl-api/test/helpers.py @@ -0,0 +1,22 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import sys + +import mozpack.path as mozpath + +setup_called = False + + +def setup(): + """Add the directory of the targeted python modules to the python sys.path""" + + global setup_called + if setup_called: + return + setup_called = True + + OUR_DIR = mozpath.abspath(mozpath.dirname(__file__)) + TARGET_MOD_DIR = mozpath.normpath(mozpath.join(OUR_DIR, "..")) + sys.path.append(TARGET_MOD_DIR) diff --git a/toolkit/components/extensions/webidl-api/test/python.toml b/toolkit/components/extensions/webidl-api/test/python.toml new file mode 100644 index 0000000000..b46d5aa895 --- /dev/null +++ b/toolkit/components/extensions/webidl-api/test/python.toml @@ -0,0 +1,10 @@ +[DEFAULT] +subsuite = "webext-python" + +["test_all_schemas_smoketest.py"] + +["test_json_schema_parsing.py"] + +["test_json_schema_platform_diffs.py"] + +["test_webidl_from_json_schema.py"] diff --git a/toolkit/components/extensions/webidl-api/test/test_all_schemas_smoketest.py b/toolkit/components/extensions/webidl-api/test/test_all_schemas_smoketest.py new file mode 100644 index 0000000000..f0ab6d496e --- /dev/null +++ b/toolkit/components/extensions/webidl-api/test/test_all_schemas_smoketest.py @@ -0,0 +1,22 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import helpers # Import test helpers module. +import mozunit + +helpers.setup() + +from GenerateWebIDLBindings import load_and_parse_JSONSchema + + +def test_all_jsonschema_load_and_parse_smoketest(): + """Make sure it can load and parse all JSONSchema files successfully""" + schemas = load_and_parse_JSONSchema() + assert schemas + assert len(schemas.json_schemas) > 0 + assert len(schemas.api_namespaces) > 0 + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/extensions/webidl-api/test/test_json_schema_parsing.py b/toolkit/components/extensions/webidl-api/test/test_json_schema_parsing.py new file mode 100644 index 0000000000..79b59bf928 --- /dev/null +++ b/toolkit/components/extensions/webidl-api/test/test_json_schema_parsing.py @@ -0,0 +1,215 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import json +import os +from textwrap import dedent + +import helpers # Import test helpers module. +import mozunit +import pytest + +helpers.setup() + +from GenerateWebIDLBindings import APIEvent, APIFunction, APINamespace, APIType, Schemas + + +def test_parse_simple_single_api_namespace(write_jsonschema_fixtures): + """ + Test Basic loading and parsing a single API JSONSchema: + - single line comments outside of the json structure are ignored + - parse a simple namespace that includes one permission, type, + function and event + """ + schema_dir = write_jsonschema_fixtures( + { + "test_api.json": dedent( + """ + // Single line comments added before the JSON data are tolerated + // and ignored. + [ + { + "namespace": "fantasyApi", + "permissions": ["fantasyPermission"], + "types": [ + { + "id": "MyType", + "type": "string", + "choices": ["value1", "value2"] + } + ], + "functions": [ + { + "name": "myMethod", + "type": "function", + "parameters": [ + { "name": "fnParam", "type": "string" }, + { "name": "fnRefParam", "$ref": "MyType" } + ] + } + ], + "events": [ + { + "name": "onSomeEvent", + "type": "function", + "parameters": [ + { "name": "evParam", "type": "string" }, + { "name": "evRefParam", "$ref": "MyType" } + ] + } + ] + } + ] + """ + ), + } + ) + + schemas = Schemas() + schemas.load_schemas(schema_dir, "toolkit") + + assert schemas.get_all_namespace_names() == ["fantasyApi"] + + apiNs = schemas.api_namespaces["fantasyApi"] + assert isinstance(apiNs, APINamespace) + + # Properties related to where the JSON schema is coming from + # (toolkit, browser or mobile schema directories). + assert apiNs.in_toolkit + assert not apiNs.in_browser + assert not apiNs.in_mobile + + # api_path_string is expected to be exactly the namespace name for + # non-nested API namespaces. + assert apiNs.api_path_string == "fantasyApi" + + # parse the schema and verify it includes the expected types events and function. + schemas.parse_schemas() + + assert set(["fantasyPermission"]) == apiNs.permissions + assert ["MyType"] == list(apiNs.types.keys()) + assert ["myMethod"] == list(apiNs.functions.keys()) + assert ["onSomeEvent"] == list(apiNs.events.keys()) + + type_entry = apiNs.types.get("MyType") + fn_entry = apiNs.functions.get("myMethod") + ev_entry = apiNs.events.get("onSomeEvent") + + assert isinstance(type_entry, APIType) + assert isinstance(fn_entry, APIFunction) + assert isinstance(ev_entry, APIEvent) + + +def test_parse_error_on_types_without_id_or_extend( + base_schema, write_jsonschema_fixtures +): + """ + Test parsing types without id or $extend raise an error while parsing. + """ + schema_dir = write_jsonschema_fixtures( + { + "test_broken_types.json": json.dumps( + [ + { + **base_schema(), + "namespace": "testBrokenTypeAPI", + "types": [ + { + # type with no "id2 or "$ref" properties + } + ], + } + ] + ) + } + ) + + schemas = Schemas() + schemas.load_schemas(schema_dir, "toolkit") + + with pytest.raises( + Exception, + match=r"Error loading schema type data defined in 'toolkit testBrokenTypeAPI'", + ): + schemas.parse_schemas() + + +def test_parse_ignores_unsupported_types(base_schema, write_jsonschema_fixtures): + """ + Test parsing types without id or $extend raise an error while parsing. + """ + schema_dir = write_jsonschema_fixtures( + { + "test_broken_types.json": json.dumps( + [ + { + **base_schema(), + "namespace": "testUnsupportedTypesAPI", + "types": [ + { + "id": "AnUnsupportedType", + "type": "string", + "unsupported": True, + }, + { + # missing id or $ref shouldn't matter + # no parsing error expected. + "unsupported": True, + }, + {"id": "ASupportedType", "type": "string"}, + ], + } + ] + ) + } + ) + + schemas = Schemas() + schemas.load_schemas(schema_dir, "toolkit") + schemas.parse_schemas() + apiNs = schemas.api_namespaces["testUnsupportedTypesAPI"] + assert set(apiNs.types.keys()) == set(["ASupportedType"]) + + +def test_parse_error_on_namespace_with_inconsistent_max_manifest_version( + base_schema, write_jsonschema_fixtures, tmpdir +): + """ + Test parsing types without id or $extend raise an error while parsing. + """ + browser_schema_dir = os.path.join(tmpdir, "browser") + mobile_schema_dir = os.path.join(tmpdir, "mobile") + os.mkdir(browser_schema_dir) + os.mkdir(mobile_schema_dir) + + base_namespace_schema = { + **base_schema(), + "namespace": "testInconsistentMaxManifestVersion", + } + + browser_schema = {**base_namespace_schema, "max_manifest_version": 2} + mobile_schema = {**base_namespace_schema, "max_manifest_version": 3} + + write_jsonschema_fixtures( + {"test_inconsistent_maxmv.json": json.dumps([browser_schema])}, + browser_schema_dir, + ) + + write_jsonschema_fixtures( + {"test_inconsistent_maxmv.json": json.dumps([mobile_schema])}, mobile_schema_dir + ) + + schemas = Schemas() + schemas.load_schemas(browser_schema_dir, "browser") + schemas.load_schemas(mobile_schema_dir, "mobile") + + with pytest.raises( + TypeError, + match=r"Error loading schema data - overwriting existing max_manifest_version value", + ): + schemas.parse_schemas() + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/extensions/webidl-api/test/test_json_schema_platform_diffs.py b/toolkit/components/extensions/webidl-api/test/test_json_schema_platform_diffs.py new file mode 100644 index 0000000000..51ae918eb0 --- /dev/null +++ b/toolkit/components/extensions/webidl-api/test/test_json_schema_platform_diffs.py @@ -0,0 +1,153 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import types +from textwrap import dedent + +import helpers # Import test helpers module. +import mozunit + +helpers.setup() + +from GenerateWebIDLBindings import Schemas +from InspectJSONSchema import run_inspect_command + + +def test_inspect_schema_platform_diffs(capsys, write_jsonschema_fixtures, tmpdir): + """ + Test InspectJSONSchema --dump-platform-diff command. + """ + browser_schema_dir = os.path.join(tmpdir, "browser") + mobile_schema_dir = os.path.join(tmpdir, "mobile") + os.mkdir(browser_schema_dir) + os.mkdir(mobile_schema_dir) + + write_jsonschema_fixtures( + { + "test_api_browser.json": dedent( + """ + [ + { + "namespace": "apiWithDiff", + "functions": [ + { + "name": "sharedMethod", + "type": "function", + "parameters": [ + { "name": "sharedParam", "type": "string" }, + { "name": "desktopOnlyMethodParam", "type": "string" } + ] + }, + { + "name": "desktopMethod", + "type": "function", + "parameters": [] + } + ], + "events": [ + { + "name": "onSharedEvent", + "type": "function", + "parameters": [ + { "name": "sharedParam", "type": "string" }, + { "name": "desktopOnlyEventParam", "type": "string" } + ] + } + ], + "properties": { + "sharedProperty": { "type": "string", "value": "desktop-value" }, + "desktopOnlyProperty": { "type": "string", "value": "desktop-only-value" } + } + } + ] + """ + ) + }, + browser_schema_dir, + ) + + write_jsonschema_fixtures( + { + "test_api_mobile.json": dedent( + """ + [ + { + "namespace": "apiWithDiff", + "functions": [ + { + "name": "sharedMethod", + "type": "function", + "parameters": [ + { "name": "sharedParam", "type": "string" }, + { "name": "mobileOnlyMethodParam", "type": "string" } + ] + }, + { + "name": "mobileMethod", + "type": "function", + "parameters": [] + } + ], + "events": [ + { + "name": "onSharedEvent", + "type": "function", + "parameters": [ + { "name": "sharedParam", "type": "string" }, + { "name": "mobileOnlyEventParam", "type": "string" } + ] + } + ], + "properties": { + "sharedProperty": { "type": "string", "value": "mobile-value" }, + "mobileOnlyProperty": { "type": "string", "value": "mobile-only-value" } + } + } + ] + """ + ) + }, + mobile_schema_dir, + ) + + schemas = Schemas() + schemas.load_schemas(browser_schema_dir, "browser") + schemas.load_schemas(mobile_schema_dir, "mobile") + + assert schemas.get_all_namespace_names() == ["apiWithDiff"] + apiNs = schemas.api_namespaces["apiWithDiff"] + assert apiNs.in_browser + assert apiNs.in_mobile + + apiNs.parse_schemas() + + fakeArgs = types.SimpleNamespace() + fakeArgs.dump_namespaces_info = False + fakeArgs.dump_platform_diffs = True + fakeArgs.only_if_webidl_diffs = False + fakeArgs.diff_command = None + + fakeParser = types.SimpleNamespace() + fakeParser.print_help = lambda: None + + run_inspect_command(fakeArgs, schemas, fakeParser) + + captured = capsys.readouterr() + assert "API schema desktop vs. mobile for apiWithDiff.sharedMethod" in captured.out + assert '- "name": "desktopOnlyMethodParam",' in captured.out + assert '+ "name": "mobileOnlyMethodParam",' in captured.out + assert "API schema desktop vs. mobile for apiWithDiff.onSharedEvent" in captured.out + assert '- "name": "desktopOnlyEventParam",' in captured.out + assert '+ "name": "mobileOnlyEventParam",' in captured.out + assert ( + "API schema desktop vs. mobile for apiWithDiff.sharedProperty" in captured.out + ) + assert '- "value": "desktop-value"' in captured.out + assert '+ "value": "mobile-value"' in captured.out + assert captured.err == "" + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/extensions/webidl-api/test/test_webidl_from_json_schema.py b/toolkit/components/extensions/webidl-api/test/test_webidl_from_json_schema.py new file mode 100644 index 0000000000..64cd7a7361 --- /dev/null +++ b/toolkit/components/extensions/webidl-api/test/test_webidl_from_json_schema.py @@ -0,0 +1,110 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from textwrap import dedent + +import helpers # Import test helpers module. +import mozunit + +helpers.setup() + +from GenerateWebIDLBindings import ( + WEBEXT_STUBS_MAPPING, + APIFunction, + Schemas, + WebIDLHelpers, +) + +original_stub_mapping_config = WEBEXT_STUBS_MAPPING.copy() + + +def teardown_function(): + WEBEXT_STUBS_MAPPING.clear() + for key in original_stub_mapping_config: + WEBEXT_STUBS_MAPPING[key] = original_stub_mapping_config[key] + + +def test_ambiguous_stub_mappings(write_jsonschema_fixtures): + """ + Test generated webidl for methods that are either + - being marked as ambiguous because of the "allowAmbiguousOptionalArguments" property + in their JSONSchema definition + - mapped to "AsyncAmbiguous" stub per WEBEXT_STUBS_MAPPING python script config + """ + + schema_dir = write_jsonschema_fixtures( + { + "test_api.json": dedent( + """ + [ + { + "namespace": "testAPINamespace", + "functions": [ + { + "name": "jsonSchemaAmbiguousMethod", + "type": "function", + "allowAmbiguousOptionalArguments": true, + "async": true, + "parameters": [ + {"type": "any", "name": "param1", "optional": true}, + {"type": "any", "name": "param2", "optional": true}, + {"type": "string", "name": "param3", "optional": true} + ] + }, + { + "name": "configuredAsAmbiguousMethod", + "type": "function", + "async": "callback", + "parameters": [ + {"name": "param1", "optional": true, "type": "object"}, + {"name": "callback", "type": "function", "parameters": []} + ] + } + ] + } + ] + """ + ) + } + ) + + assert "testAPINamespace.configuredAsAmbiguousMethod" not in WEBEXT_STUBS_MAPPING + # NOTE: mocked config reverted in the teardown_method pytest hook. + WEBEXT_STUBS_MAPPING[ + "testAPINamespace.configuredAsAmbiguousMethod" + ] = "AsyncAmbiguous" + + schemas = Schemas() + schemas.load_schemas(schema_dir, "toolkit") + + assert schemas.get_all_namespace_names() == ["testAPINamespace"] + schemas.parse_schemas() + + apiNs = schemas.get_namespace("testAPINamespace") + fnAmbiguousBySchema = apiNs.functions.get("jsonSchemaAmbiguousMethod") + + assert isinstance(fnAmbiguousBySchema, APIFunction) + generated_webidl = WebIDLHelpers.to_webidl_definition(fnAmbiguousBySchema, None) + expected_webidl = "\n".join( + [ + ' [Throws, WebExtensionStub="AsyncAmbiguous"]', + " any jsonSchemaAmbiguousMethod(any... args);", + ] + ) + assert generated_webidl == expected_webidl + + fnAmbiguousByConfig = apiNs.functions.get("configuredAsAmbiguousMethod") + assert isinstance(fnAmbiguousByConfig, APIFunction) + generated_webidl = WebIDLHelpers.to_webidl_definition(fnAmbiguousByConfig, None) + expected_webidl = "\n".join( + [ + ' [Throws, WebExtensionStub="AsyncAmbiguous"]', + " any configuredAsAmbiguousMethod(any... args);", + ] + ) + assert generated_webidl == expected_webidl + + +if __name__ == "__main__": + mozunit.main() |