Skip to content

Commit c7a0120

Browse files
feat(nimbus): Add a test that loads all FML manifests (#15090)
Because: - we are constantly adding and updating feature manifests; - we are constantly updating our application-services dependency; and - we have no tests that validate that existing manifests are valid in these cases this commit: - adds a test case per application that validates the vendored FML files are loadable. Fixes #15020
1 parent e650d3b commit c7a0120

File tree

4 files changed

+80
-18
lines changed

4 files changed

+80
-18
lines changed

experimenter/experimenter/experiments/constants.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -298,19 +298,19 @@ class Application(models.TextChoices):
298298
)
299299

300300
@staticmethod
301-
def is_sdk(application):
302-
return application != Application.DESKTOP
301+
def is_sdk(slug: str):
302+
return slug != Application.DESKTOP
303303

304304
@staticmethod
305-
def is_mobile(application):
306-
return application in (
305+
def is_mobile(slug: str):
306+
return slug in (
307307
Application.FENIX,
308308
Application.IOS,
309309
)
310310

311311
@staticmethod
312-
def is_web(application):
313-
return application in (
312+
def is_web(slug: str):
313+
return slug in (
314314
Application.DEMO_APP,
315315
Application.MONITOR,
316316
Application.FXA,

experimenter/experimenter/experiments/tests/api/v6/test_serializers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ class TestNimbusExperimentSerializer(TestCase):
2727
@classmethod
2828
def _validate_experiment_schema(
2929
cls,
30-
application: NimbusExperiment.Application,
30+
application_slug: str,
3131
experiment_data: dict[str, Any],
3232
):
33-
if NimbusExperiment.Application.is_sdk(application):
33+
if NimbusExperiment.Application.is_sdk(application_slug):
3434
schema = SdkNimbusExperiment
3535
else:
3636
schema = DesktopAllVersionsNimbusExperiment

experimenter/experimenter/features/manifests/nimbus_fml_loader.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,27 @@ def fml_client(self, version: Optional[NimbusFeatureVersion] = None) -> FmlClien
5555
"""
5656
file_path = self.file_path(version)
5757
if file_path is not None:
58-
try:
59-
return FmlClient(
60-
str(file_path),
61-
self.channel,
62-
)
63-
except FmlError:
64-
logger.exception(
65-
f"Nimbus FML Loader: FmlClient failed to parse manifest: {file_path}"
66-
)
67-
return None
58+
return NimbusFmlLoader.get_fml_client_uncached(
59+
str(file_path),
60+
self.channel,
61+
)
6862
else:
6963
logger.error("Nimbus FML Loader: Failed to get FmlClient.")
7064
return None
7165

66+
@staticmethod
67+
def get_fml_client_uncached(file_path: Path, channel: str) -> FmlClient:
68+
try:
69+
return FmlClient(
70+
str(file_path),
71+
channel,
72+
)
73+
except FmlError:
74+
logger.exception(
75+
f"Nimbus FML Loader: FmlClient failed to parse manifest: {file_path}"
76+
)
77+
return None
78+
7279
def get_fml_errors(
7380
self,
7481
blob: str,

experimenter/experimenter/features/tests/test_nimbus_fml_loader.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import json
2+
from pathlib import Path
23
from unittest.mock import patch
34

5+
from django.conf import settings
46
from django.test import TestCase
57
from nimbus_megazord.fml import FmlClient, FmlError
8+
from parameterized import parameterized
69

710
from experimenter.experiments.constants import NimbusConstants
811
from experimenter.features.manifests.nimbus_fml_loader import NimbusFmlLoader
@@ -243,3 +246,55 @@ def test_get_fml_errors_returns_empty_when_manifest_fails_to_parse(
243246
"Nimbus FML Loader: FmlClient failed to parse manifest",
244247
log.output[0],
245248
)
249+
250+
251+
class FeatureManifestTests(TestCase):
252+
@classmethod
253+
def setUpClass(cls):
254+
NimbusFmlLoader.create_loader.cache_clear()
255+
NimbusFmlLoader.fml_client.cache_clear()
256+
257+
@classmethod
258+
def tearDownClass(cls):
259+
NimbusFmlLoader.create_loader.cache_clear()
260+
NimbusFmlLoader.fml_client.cache_clear()
261+
262+
@staticmethod
263+
def _discover_fml_files(application):
264+
return [
265+
(path, channel)
266+
for (path, channel) in (
267+
(path, path.name.split(".", maxsplit=1)[0])
268+
for path in (
269+
root_path / file_name
270+
for (root_path, _, file_names) in Path.walk(
271+
settings.FEATURE_MANIFESTS_PATH / application.slug
272+
)
273+
for file_name in file_names
274+
if file_name.endswith(".fml.yaml")
275+
)
276+
)
277+
if channel in application.channel_app_id
278+
]
279+
280+
@parameterized.expand(
281+
[
282+
application
283+
for application in NimbusConstants.APPLICATION_CONFIGS.values()
284+
if NimbusConstants.Application.is_sdk(application.slug)
285+
]
286+
)
287+
def test_fml_loads(self, application):
288+
for file_path, channel in FeatureManifestTests._discover_fml_files(application):
289+
client = NimbusFmlLoader.get_fml_client_uncached(
290+
str(file_path),
291+
channel,
292+
)
293+
294+
self.assertIsNotNone(
295+
client,
296+
(
297+
f"`{file_path}' is a valid manifest for `{application}' on channel "
298+
f"`{channel}'"
299+
),
300+
)

0 commit comments

Comments
 (0)