1use glob::MatchOptions;
6use std::collections::HashSet;
7
8use super::commands::{
9 GenerateExperimenterManifestCmd, GenerateSingleFileManifestCmd, GenerateStructCmd,
10 PrintChannelsCmd, PrintInfoCmd, ValidateCmd,
11};
12use crate::backends::info::ManifestInfo;
13use crate::error::FMLError::CliError;
14use crate::frontend::ManifestFrontEnd;
15use crate::{
16 backends,
17 error::{FMLError, Result},
18 intermediate_representation::{FeatureManifest, TargetLanguage},
19 parser::Parser,
20 util::loaders::{FileLoader, FilePath, LoaderConfig},
21};
22use std::io::Write;
23use std::path::Path;
24use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
25
26const MATCHING_FML_EXTENSION: &str = ".fml.yaml";
28
29pub(crate) fn generate_struct(cmd: &GenerateStructCmd) -> Result<()> {
30 let files: FileLoader = TryFrom::try_from(&cmd.loader)?;
31
32 let filename = &cmd.manifest;
33 let input = files.file_path(filename)?;
34
35 match (&input, &cmd.output.is_dir()) {
36 (FilePath::Remote(_), _) => generate_struct_single(&files, input, cmd),
37 (FilePath::Local(file), _) if file.is_file() => generate_struct_single(&files, input, cmd),
38 (FilePath::Local(dir), true) if dir.is_dir() => generate_struct_from_dir(&files, cmd, dir),
39 (_, true) => generate_struct_from_glob(&files, cmd, filename),
40 _ => Err(FMLError::CliError(
41 "Cannot generate a single output file from an input directory".to_string(),
42 )),
43 }
44}
45
46fn generate_struct_from_dir(files: &FileLoader, cmd: &GenerateStructCmd, cwd: &Path) -> Result<()> {
47 let entries = cwd.read_dir()?;
48 for entry in entries.filter_map(Result::ok) {
49 let pb = entry.path();
50 if pb.is_dir() {
51 generate_struct_from_dir(files, cmd, &pb)?;
52 } else if let Some(nm) = pb.file_name().map(|s| s.to_str().unwrap_or_default()) {
53 if nm.ends_with(MATCHING_FML_EXTENSION) {
54 let path = pb.as_path().into();
55 generate_struct_single(files, path, cmd)?;
56 }
57 }
58 }
59 Ok(())
60}
61
62fn generate_struct_from_glob(
63 files: &FileLoader,
64 cmd: &GenerateStructCmd,
65 pattern: &str,
66) -> Result<()> {
67 use glob::glob_with;
68 let entries = glob_with(pattern, MatchOptions::new()).unwrap();
69 for entry in entries.filter_map(Result::ok) {
70 let path = entry.as_path().into();
71 generate_struct_single(files, path, cmd)?;
72 }
73 Ok(())
74}
75
76fn generate_struct_single(
77 files: &FileLoader,
78 manifest_path: FilePath,
79 cmd: &GenerateStructCmd,
80) -> Result<()> {
81 let ir = load_feature_manifest(
82 files.clone(),
83 manifest_path,
84 cmd.load_from_ir,
85 Some(&cmd.channel),
86 )?;
87 generate_struct_from_ir(&ir, cmd)
88}
89
90fn generate_struct_from_ir(ir: &FeatureManifest, cmd: &GenerateStructCmd) -> Result<()> {
91 let language = &cmd.language;
92 ir.validate_manifest_for_lang(language)?;
93 match language {
94 TargetLanguage::IR => {
95 let contents = serde_json::to_string_pretty(&ir)?;
96 std::fs::write(&cmd.output, contents)?;
97 }
98 TargetLanguage::Kotlin => backends::kotlin::generate_struct(ir, cmd)?,
99 TargetLanguage::Swift => backends::swift::generate_struct(ir, cmd)?,
100 _ => unimplemented!(
101 "Unsupported output language for structs: {}",
102 language.extension()
103 ),
104 };
105 Ok(())
106}
107
108pub(crate) fn generate_experimenter_manifest(cmd: &GenerateExperimenterManifestCmd) -> Result<()> {
109 let files: FileLoader = TryFrom::try_from(&cmd.loader)?;
110 let path = files.file_path(&cmd.manifest)?;
111 let ir = load_feature_manifest(files, path, cmd.load_from_ir, None)?;
112 backends::experimenter_manifest::generate_manifest(ir, cmd)?;
113 Ok(())
114}
115
116pub(crate) fn generate_single_file_manifest(cmd: &GenerateSingleFileManifestCmd) -> Result<()> {
117 let files: FileLoader = TryFrom::try_from(&cmd.loader)?;
118 let path = files.file_path(&cmd.manifest)?;
119 let fm = load_feature_manifest(files, path, false, Some(&cmd.channel))?;
120 let frontend: ManifestFrontEnd = fm.into();
121 std::fs::write(&cmd.output, serde_yaml::to_string(&frontend)?)?;
122 Ok(())
123}
124
125fn load_feature_manifest(
126 files: FileLoader,
127 path: FilePath,
128 load_from_ir: bool,
129 channel: Option<&str>,
130) -> Result<FeatureManifest> {
131 let ir = if !load_from_ir {
132 let parser: Parser = Parser::new(files, path)?;
133 parser.get_intermediate_representation(channel)?
134 } else {
135 files.read::<FeatureManifest>(&path)?
136 };
137 ir.validate_manifest()?;
138 Ok(ir)
139}
140
141pub(crate) fn fetch_file(files: &LoaderConfig, nm: &str) -> Result<()> {
142 let files: FileLoader = files.try_into()?;
143 let file = files.file_path(nm)?;
144
145 let string = files.read_to_string(&file)?;
146
147 println!("{}", string);
148 Ok(())
149}
150
151fn output_ok(stream: &mut StandardStream, title: &str) -> Result<()> {
152 write!(stream, "✅ ")?;
153 stream.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
154 writeln!(stream, "{title}")?;
155 stream.reset()?;
156
157 Ok(())
158}
159
160fn output_note(stream: &mut StandardStream, title: &str) -> Result<()> {
161 write!(stream, "ℹ️ ")?;
162 stream.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
163 writeln!(stream, "{title}")?;
164 stream.reset()?;
165
166 Ok(())
167}
168
169fn output_warn(stream: &mut StandardStream, title: &str, detail: &str) -> Result<()> {
170 write!(stream, "⚠️ ")?;
171 stream.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
172 write!(stream, "{title}")?;
173 stream.reset()?;
174 writeln!(stream, ": {detail}")?;
175 Ok(())
176}
177
178fn output_err(stream: &mut StandardStream, title: &str, detail: &str) -> Result<()> {
179 writeln!(stream, "❎ ")?;
180 stream.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
181 writeln!(stream, "{title}")?;
182 stream.reset()?;
183 writeln!(stream, ": {detail}")?;
184
185 Ok(())
186}
187
188pub(crate) fn validate(cmd: &ValidateCmd) -> Result<()> {
189 let mut stdout = StandardStream::stdout(ColorChoice::Auto);
190
191 let files: FileLoader = TryFrom::try_from(&cmd.loader)?;
192
193 let filename = &cmd.manifest;
194 let file_path = files.file_path(filename)?;
195 let parser: Parser = Parser::new(files, file_path.clone())?;
196 let mut loading = HashSet::new();
197 let manifest_front_end = parser.load_manifest(&file_path, &mut loading)?;
198
199 let iter_includes = loading.iter().map(|id| id.to_string());
200
201 let channels = manifest_front_end.channels();
202 if channels.is_empty() {
203 output_note(
204 &mut stdout,
205 &format!(
206 "Loaded modules:\n- {}\n",
207 iter_includes.collect::<Vec<String>>().join("\n- ")
208 ),
209 )?;
210 output_ok(&mut stdout, &format!(
211 "{}\n{}\n{}",
212 "The manifest is valid for including in other files. To be imported, or used as an app manifest, it requires the following:",
213 "- A `channels` list",
214 "- An `about` block",
215 ))?;
216 return Ok(());
217 }
218 let intermediate_representation =
219 parser
220 .get_intermediate_representation(None)
221 .inspect_err(|e| {
222 output_err(&mut stdout, "Manifest is invalid", &e.to_string()).unwrap();
223 })?;
224
225 output_note(
226 &mut stdout,
227 &format!(
228 "Loaded modules:\n- {}\n",
229 iter_includes
230 .chain(
231 intermediate_representation
232 .all_imports
233 .keys()
234 .map(|m| m.to_string())
235 )
236 .collect::<Vec<String>>()
237 .join("\n- ")
238 ),
239 )?;
240
241 writeln!(stdout, "Validating feature metadata:")?;
242 let mut features_with_warnings = 0;
243 for (_, f) in intermediate_representation.iter_all_feature_defs() {
244 let fm = &f.metadata;
245 let mut missing = vec![];
246 if fm.meta_bug.is_none() {
247 missing.push("'meta-bug'");
248 }
249 if fm.documentation.is_empty() {
250 missing.push("'documentation'");
251 }
252 if fm.contacts.is_empty() {
253 missing.push("'contacts'");
254 }
255 if !missing.is_empty() {
256 output_warn(
257 &mut stdout,
258 &format!("'{}' missing metadata", &f.name),
259 &missing.join(", "),
260 )?;
261 features_with_warnings += 1;
262 }
263 }
264
265 if features_with_warnings == 0 {
266 output_ok(&mut stdout, "All feature metadata ok\n")?;
267 } else {
268 let features = if features_with_warnings == 1 {
269 "feature"
270 } else {
271 "features"
272 };
273 writeln!(
274 &mut stdout,
275 "Each feature should have entries for at least:"
276 )?;
277 writeln!(&mut stdout, " - meta-bug: a URL where to file bugs")?;
278 writeln!(
279 &mut stdout,
280 " - documentation: a list of one or more URLs documenting the feature"
281 )?;
282 writeln!(&mut stdout, " e.g. QA docs, user docs")?;
283 writeln!(
284 &mut stdout,
285 " - contacts: a list of one or more email addresses"
286 )?;
287 writeln!(&mut stdout, " (with Mozilla Jira accounts)")?;
288 writeln!(
289 &mut stdout,
290 "Metadata warnings detected in {features_with_warnings} {features}\n"
291 )?;
292 }
293
294 writeln!(&mut stdout, "Validating manifest for different channels:")?;
295
296 let results = channels
297 .iter()
298 .map(|c| {
299 let intermediate_representation = parser.get_intermediate_representation(Some(c));
300 match intermediate_representation {
301 Ok(ir) => (c, ir.validate_manifest()),
302 Err(e) => (c, Err(e)),
303 }
304 })
305 .collect::<Vec<(&String, Result<_>)>>();
306
307 let mut error_count = 0;
308 for (channel, result) in results {
309 match result {
310 Ok(_) => {
311 output_ok(&mut stdout, &format!("{channel:.<20}valid"))?;
312 }
313 Err(e) => {
314 error_count += 1;
315 output_err(
316 &mut stdout,
317 &format!("{channel:.<20}invalid"),
318 &e.to_string(),
319 )?;
320 }
321 };
322 }
323
324 if error_count > 0 {
325 return Err(CliError(format!(
326 "Manifest contains error(s) in {} channel{}",
327 error_count,
328 if error_count > 1 { "s" } else { "" }
329 )));
330 }
331
332 Ok(())
333}
334
335pub(crate) fn print_channels(cmd: &PrintChannelsCmd) -> Result<()> {
336 let files = TryFrom::try_from(&cmd.loader)?;
337 let manifest = Parser::load_frontend(files, &cmd.manifest)?;
338 let channels = manifest.channels();
339 if cmd.as_json {
340 let json = serde_json::Value::from(channels);
341 println!("{}", json);
342 } else {
343 println!("{}", channels.join("\n"));
344 }
345 Ok(())
346}
347
348pub(crate) fn print_info(cmd: &PrintInfoCmd) -> Result<()> {
349 let files: FileLoader = TryFrom::try_from(&cmd.loader)?;
350 let path = files.file_path(&cmd.manifest)?;
351 let fm = load_feature_manifest(files, path.clone(), false, cmd.channel.as_deref())?;
352 let info = if let Some(feature_id) = &cmd.feature {
353 ManifestInfo::from_feature(&path, &fm, feature_id)?
354 } else {
355 ManifestInfo::from(&path, &fm)
356 };
357 if cmd.as_json {
358 println!("{}", info.to_json()?);
359 } else {
360 println!("{}", info.to_yaml()?);
361 }
362 Ok(())
363}
364
365#[cfg(all(
366 test,
367 any(feature = "swift-tests", feature = "kotlin-tests"),
368 not(feature = "all-features-workaround")
369))]
370mod test {
371 use std::fs;
372 use std::path::PathBuf;
373
374 use anyhow::anyhow;
375 use jsonschema::JSONSchema;
376
377 use super::*;
378 use crate::backends::experimenter_manifest::ExperimenterManifest;
379 use crate::backends::{kotlin, swift};
380 use crate::frontend::AboutBlock;
381 use crate::util::{generated_src_dir, join, pkg_dir};
382
383 const MANIFEST_PATHS: &[&str] = &[
384 "fixtures/ir/simple_nimbus_validation.json",
385 "fixtures/ir/simple_nimbus_validation.json",
386 "fixtures/ir/with_objects.json",
387 "fixtures/ir/full_homescreen.json",
388 "fixtures/fe/importing/simple/app.yaml",
389 "fixtures/fe/importing/diamond/00-app.yaml",
390 ];
391
392 pub(crate) fn generate_and_assert(
393 test_script: &str,
394 manifest: &str,
395 channel: &str,
396 is_ir: bool,
397 ) -> Result<()> {
398 let cmd = create_command_from_test(test_script, manifest, channel, is_ir)?;
399 generate_struct(&cmd)?;
400 run_script_with_generated_code(
401 &cmd.language,
402 &[cmd.output.as_path().display().to_string()],
403 test_script,
404 )?;
405 Ok(())
406 }
407
408 fn generate_struct_cli_overrides(from_cli: AboutBlock, cmd: &GenerateStructCmd) -> Result<()> {
409 let files: FileLoader = TryFrom::try_from(&cmd.loader)?;
410 let path = files.file_path(&cmd.manifest)?;
411 let mut ir = load_feature_manifest(files, path, cmd.load_from_ir, Some(&cmd.channel))?;
412
413 let from_file = ir.about;
417 let kotlin_about = from_cli.kotlin_about.or(from_file.kotlin_about);
418 let swift_about = from_cli.swift_about.or(from_file.swift_about);
419 let about = AboutBlock {
420 kotlin_about,
421 swift_about,
422 ..Default::default()
423 };
424 ir.about = about;
425
426 generate_struct_from_ir(&ir, cmd)
427 }
428
429 pub(crate) fn generate_and_assert_with_config(
432 test_script: &str,
433 manifest: &str,
434 channel: &str,
435 is_ir: bool,
436 config_about: AboutBlock,
437 ) -> Result<()> {
438 let cmd = create_command_from_test(test_script, manifest, channel, is_ir)?;
439 generate_struct_cli_overrides(config_about, &cmd)?;
440 run_script_with_generated_code(
441 &cmd.language,
442 &[cmd.output.as_path().display().to_string()],
443 test_script,
444 )?;
445 Ok(())
446 }
447
448 pub(crate) fn create_command_from_test(
449 test_script: &str,
450 manifest: &str,
451 channel: &str,
452 is_ir: bool,
453 ) -> Result<GenerateStructCmd, crate::error::FMLError> {
454 let test_script = join(pkg_dir(), test_script);
455 let pbuf = PathBuf::from(&test_script);
456 let ext = pbuf
457 .extension()
458 .ok_or_else(|| anyhow!("Require a test_script with an extension: {}", test_script))?;
459 let language: TargetLanguage = ext.try_into()?;
460 let manifest_fml = join(pkg_dir(), manifest);
461 let file = PathBuf::from(&manifest_fml);
462 let file = file
463 .file_stem()
464 .ok_or_else(|| anyhow!("Manifest file path isn't a file"))?
465 .to_str()
466 .ok_or_else(|| anyhow!("Manifest file path isn't a file with a sensible name"))?;
467 let stem = pbuf.file_stem().unwrap().to_str().unwrap();
468 fs::create_dir_all(join(generated_src_dir(), stem))?;
469 let manifest_out = format!(
470 "{}/{stem}/{file}_{channel}.{}",
471 generated_src_dir(),
472 language.extension()
473 );
474 let loader = Default::default();
475 Ok(GenerateStructCmd {
476 manifest: manifest_fml,
477 output: manifest_out.into(),
478 load_from_ir: is_ir,
479 language,
480 channel: channel.into(),
481 loader,
482 })
483 }
484
485 pub(crate) fn generate_multiple_and_assert(
486 test_script: &str,
487 manifests: &[(&str, &str)],
488 ) -> Result<()> {
489 let cmds = manifests
490 .iter()
491 .map(|(manifest, channel)| {
492 let cmd = create_command_from_test(test_script, manifest, channel, false)?;
493 generate_struct(&cmd)?;
494 Ok(cmd)
495 })
496 .collect::<Result<Vec<_>>>()?;
497
498 let first = cmds
499 .first()
500 .expect("At least one manifests are always used");
501 let language = &first.language;
502
503 let manifests_out = cmds
504 .iter()
505 .map(|cmd| cmd.output.display().to_string())
506 .collect::<Vec<_>>();
507
508 run_script_with_generated_code(language, &manifests_out, test_script)?;
509 Ok(())
510 }
511
512 fn run_script_with_generated_code(
513 language: &TargetLanguage,
514 manifests_out: &[String],
515 test_script: &str,
516 ) -> Result<()> {
517 match language {
518 TargetLanguage::Kotlin => {
519 kotlin::test::run_script_with_generated_code(manifests_out, test_script)?
520 }
521 TargetLanguage::Swift => {
522 swift::test::run_script_with_generated_code(manifests_out, test_script.as_ref())?
523 }
524 _ => unimplemented!(),
525 }
526 Ok(())
527 }
528
529 #[test]
530 fn test_importing_simple_experimenter_manifest() -> Result<()> {
531 let cmd = create_experimenter_manifest_cmd("fixtures/fe/importing/simple/app.yaml")?;
533 let files = FileLoader::default()?;
534 let path = files.file_path(&cmd.manifest)?;
535 let fm = load_feature_manifest(files, path, cmd.load_from_ir, None)?;
536 let m: ExperimenterManifest = fm.try_into()?;
537
538 assert!(m.contains_key("homescreen"));
539 assert!(m.contains_key("search"));
540
541 Ok(())
542 }
543
544 fn validate_against_experimenter_schema<P: AsRef<Path>>(
545 schema_path: P,
546 generated_yaml: &serde_yaml::Value,
547 ) -> Result<()> {
548 let generated_manifest: ExperimenterManifest =
549 serde_yaml::from_value(generated_yaml.to_owned())?;
550 let generated_json = serde_json::to_value(generated_manifest)?;
551
552 let schema = fs::read_to_string(&schema_path)?;
553 let schema: serde_json::Value = serde_json::from_str(&schema)?;
554 let compiled = JSONSchema::compile(&schema).expect("The schema is invalid");
555 let res = compiled.validate(&generated_json);
556 if let Err(e) = res {
557 panic!(
558 "Validation errors: \n{}",
559 e.map(|e| e.to_string()).collect::<Vec<String>>().join("\n")
560 );
561 }
562 Ok(())
563 }
564
565 #[test]
566 fn test_schema_validation() -> Result<()> {
567 for path in MANIFEST_PATHS {
568 let cmd = create_experimenter_manifest_cmd(path)?;
569 generate_experimenter_manifest(&cmd)?;
570
571 let generated = fs::read_to_string(&cmd.output)?;
572 let generated_yaml = serde_yaml::from_str(&generated)?;
573 validate_against_experimenter_schema(
574 join(pkg_dir(), "ExperimentFeatureManifest.schema.json"),
575 &generated_yaml,
576 )?;
577 }
578 Ok(())
579 }
580
581 #[test]
582 fn test_validate_command() -> Result<()> {
583 let paths = MANIFEST_PATHS
584 .iter()
585 .filter(|p| p.ends_with(".yaml"))
586 .chain([&"fixtures/fe/no_about_no_channels.yaml"])
587 .collect::<Vec<&&str>>();
588 for path in paths {
589 let manifest = join(pkg_dir(), path);
590 let cmd = ValidateCmd {
591 loader: Default::default(),
592 manifest,
593 };
594 validate(&cmd)?;
595 }
596 Ok(())
597 }
598
599 #[test]
600 fn test_validate_command_fails_on_bad_default_value_for_one_channel() -> Result<()> {
601 let path = "fixtures/fe/invalid/invalid_default_value_for_one_channel.fml.yaml";
602 let manifest = join(pkg_dir(), path);
603 let cmd = ValidateCmd {
604 loader: Default::default(),
605 manifest,
606 };
607 let result = validate(&cmd);
608
609 assert!(result.is_err());
610
611 match result.err().unwrap() {
612 CliError(error) => {
613 assert_eq!(error, "Manifest contains error(s) in 1 channel");
614 }
615 _ => panic!("Error is not a ValidationError"),
616 };
617
618 Ok(())
619 }
620
621 fn create_experimenter_manifest_cmd(path: &str) -> Result<GenerateExperimenterManifestCmd> {
622 let manifest = join(pkg_dir(), path);
623 let file = Path::new(&manifest);
624 let filestem = file
625 .file_stem()
626 .ok_or_else(|| anyhow!("Manifest file path isn't a file"))?
627 .to_str()
628 .ok_or_else(|| anyhow!("Manifest file path isn't a file with a sensible name"))?;
629
630 fs::create_dir_all(generated_src_dir())?;
631
632 let output = join(generated_src_dir(), &format!("{filestem}.yaml")).into();
633 let load_from_ir = if let Some(ext) = file.extension() {
634 TargetLanguage::ExperimenterJSON == ext.try_into()?
635 } else {
636 false
637 };
638 let loader = Default::default();
639 Ok(GenerateExperimenterManifestCmd {
640 manifest,
641 output,
642 language: TargetLanguage::ExperimenterYAML,
643 load_from_ir,
644 loader,
645 })
646 }
647
648 fn test_single_merged_manifest_file(path: &str, channel: &str) -> Result<()> {
649 let manifest = join(pkg_dir(), path);
650 let file = Path::new(&manifest);
651 let filestem = file
652 .file_stem()
653 .ok_or_else(|| anyhow!("Manifest file path isn't a file"))?
654 .to_str()
655 .ok_or_else(|| anyhow!("Manifest file path isn't a file with a sensible name"))?;
656
657 fs::create_dir_all(generated_src_dir())?;
658
659 let output: PathBuf =
660 join(generated_src_dir(), &format!("single-file-{filestem}.yaml")).into();
661 let loader = Default::default();
662
663 let files: FileLoader = TryFrom::try_from(&loader)?;
665 let src = files.file_path(&manifest)?;
666 let fm = load_feature_manifest(files, src, false, Some(channel))?;
667 let expected = fm.default_json();
668
669 let cmd = GenerateSingleFileManifestCmd {
671 loader: Default::default(),
672 manifest,
673 output: output.clone(),
674 channel: channel.to_string(),
675 };
676 generate_single_file_manifest(&cmd)?;
677
678 let dest = FilePath::Local(output);
680 let files: FileLoader = TryFrom::try_from(&loader)?;
681 let fm = load_feature_manifest(files, dest, false, Some(channel))?;
682 let observed = fm.default_json();
683
684 assert_eq!(expected, observed);
686
687 Ok(())
688 }
689
690 #[test]
691 fn test_single_file_command() -> Result<()> {
692 test_single_merged_manifest_file("fixtures/fe/browser.yaml", "release")?;
693 test_single_merged_manifest_file(
694 "fixtures/fe/importing/including-imports/ui.fml.yaml",
695 "none",
696 )?;
697 test_single_merged_manifest_file(
698 "fixtures/fe/importing/including-imports/app.fml.yaml",
699 "release",
700 )?;
701 test_single_merged_manifest_file("fixtures/fe/importing/overrides/app.fml.yaml", "debug")?;
702 test_single_merged_manifest_file("fixtures/fe/importing/overrides/lib.fml.yaml", "debug")?;
703 test_single_merged_manifest_file("fixtures/fe/importing/diamond/00-app.yaml", "debug")?;
704 test_single_merged_manifest_file("fixtures/fe/importing/diamond/01-lib.yaml", "debug")?;
705 test_single_merged_manifest_file("fixtures/fe/importing/diamond/02-sublib.yaml", "debug")?;
706
707 test_single_merged_manifest_file("fixtures/fe/misc-features.yaml", "debug")?;
708 Ok(())
709 }
710}
711
712#[cfg(all(
713 test,
714 feature = "kotlin-tests",
715 not(feature = "all-features-workaround")
716))]
717mod kts_tests {
718 use crate::frontend::{AboutBlock, KotlinAboutBlock};
719
720 use super::{
721 test::{
722 generate_and_assert, generate_and_assert_with_config, generate_multiple_and_assert,
723 },
724 *,
725 };
726
727 #[test]
728 fn test_simple_validation_code_from_ir() -> Result<()> {
729 generate_and_assert(
730 "test/simple_nimbus_validation.kts",
731 "fixtures/ir/simple_nimbus_validation.json",
732 "release",
733 true,
734 )?;
735 Ok(())
736 }
737
738 #[test]
739 fn test_with_objects_code_from_ir() -> Result<()> {
740 generate_and_assert(
741 "test/with_objects.kts",
742 "fixtures/ir/with_objects.json",
743 "release",
744 true,
745 )?;
746 Ok(())
747 }
748
749 #[test]
750 fn test_with_full_homescreen_from_ir() -> Result<()> {
751 generate_and_assert(
752 "test/full_homescreen.kts",
753 "fixtures/ir/full_homescreen.json",
754 "release",
755 true,
756 )?;
757 Ok(())
758 }
759
760 #[test]
761 fn test_with_full_fenix_release() -> Result<()> {
762 generate_and_assert_with_config(
763 "test/fenix_release.kts",
764 "fixtures/fe/browser.yaml",
765 "release",
766 false,
767 AboutBlock {
768 kotlin_about: Some(KotlinAboutBlock {
769 package: "com.example.app".to_string(),
770 class: "com.example.release.FxNimbus".to_string(),
771 }),
772 swift_about: None,
773 ..Default::default()
774 },
775 )?;
776 Ok(())
777 }
778
779 #[test]
780 fn test_with_full_fenix_nightly() -> Result<()> {
781 generate_and_assert_with_config(
782 "test/fenix_nightly.kts",
783 "fixtures/fe/browser.yaml",
784 "nightly",
785 false,
786 AboutBlock {
787 kotlin_about: Some(KotlinAboutBlock {
788 package: "com.example.app".to_string(),
789 class: "com.example.nightly.FxNimbus".to_string(),
790 }),
791 swift_about: None,
792 ..Default::default()
793 },
794 )?;
795 Ok(())
796 }
797
798 #[test]
799 fn test_with_full_fenix_nightly_with_prefs() -> Result<()> {
800 generate_and_assert_with_config(
801 "test/fenix_nightly.kts",
802 "fixtures/fe/browser-with-prefs.yaml",
803 "nightly",
804 false,
805 AboutBlock {
806 kotlin_about: Some(KotlinAboutBlock {
807 package: "com.example.app".to_string(),
808 class: "com.example.nightly.FxNimbus".to_string(),
809 }),
810 swift_about: None,
811 ..Default::default()
812 },
813 )?;
814 Ok(())
815 }
816
817 #[test]
818 fn test_with_dx_improvements() -> Result<()> {
819 generate_and_assert(
820 "test/dx_improvements_testing.kts",
821 "fixtures/fe/dx_improvements.yaml",
822 "testing",
823 false,
824 )?;
825 Ok(())
826 }
827
828 #[test]
829 fn test_with_app_menu_from_ir() -> Result<()> {
830 generate_and_assert(
831 "test/app_menu.kts",
832 "fixtures/ir/app_menu.json",
833 "release",
834 true,
835 )?;
836 Ok(())
837 }
838
839 #[test]
840 fn test_with_bundled_resources_kts() -> Result<()> {
841 generate_and_assert(
842 "test/bundled_resources.kts",
843 "fixtures/fe/bundled_resouces.yaml",
844 "testing",
845 false,
846 )?;
847 Ok(())
848 }
849
850 #[test]
851 fn test_importing_simple_kts() -> Result<()> {
852 generate_multiple_and_assert(
853 "test/importing/simple/app_debug.kts",
854 &[
855 ("fixtures/fe/importing/simple/lib.yaml", "debug"),
856 ("fixtures/fe/importing/simple/app.yaml", "debug"),
857 ],
858 )?;
859 Ok(())
860 }
861
862 #[test]
863 fn test_importing_channel_mismatching_kts() -> Result<()> {
864 generate_multiple_and_assert(
865 "test/importing/channels/app_debug.kts",
866 &[
867 ("fixtures/fe/importing/channels/app.fml.yaml", "app-debug"),
868 ("fixtures/fe/importing/channels/lib.fml.yaml", "debug"),
869 ],
870 )?;
871 Ok(())
872 }
873
874 #[test]
875 fn test_importing_override_defaults_kts() -> Result<()> {
876 generate_multiple_and_assert(
877 "test/importing/overrides/app_debug.kts",
878 &[
879 ("fixtures/fe/importing/overrides/app.fml.yaml", "debug"),
880 ("fixtures/fe/importing/overrides/lib.fml.yaml", "debug"),
881 ],
882 )?;
883 Ok(())
884 }
885
886 #[test]
887 fn test_importing_override_defaults_coverall_kts() -> Result<()> {
888 generate_multiple_and_assert(
889 "test/importing/overrides-coverall/app_debug.kts",
890 &[
891 (
892 "fixtures/fe/importing/overrides-coverall/app.fml.yaml",
893 "debug",
894 ),
895 (
896 "fixtures/fe/importing/overrides-coverall/lib.fml.yaml",
897 "debug",
898 ),
899 ],
900 )?;
901 Ok(())
902 }
903
904 #[test]
905 fn test_importing_diamond_overrides_kts() -> Result<()> {
906 generate_multiple_and_assert(
910 "test/importing/diamond/00-app.kts",
911 &[
912 ("fixtures/fe/importing/diamond/00-app.yaml", "debug"),
913 ("fixtures/fe/importing/diamond/01-lib.yaml", "debug"),
914 ("fixtures/fe/importing/diamond/02-sublib.yaml", "debug"),
915 ],
916 )?;
917 Ok(())
918 }
919
920 #[test]
921 #[ignore]
922 fn test_importing_reexporting_features() -> Result<()> {
923 generate_multiple_and_assert(
929 "test/importing/reexporting/00-app.kts",
930 &[
931 ("fixtures/fe/importing/reexporting/00-app.yaml", "debug"),
932 ("fixtures/fe/importing/reexporting/01-lib.yaml", "debug"),
933 ("fixtures/fe/importing/reexporting/02-sublib.yaml", "debug"),
934 ],
935 )?;
936 Ok(())
937 }
938
939 #[test]
940 fn test_importing_including_imports_kts() -> Result<()> {
941 generate_multiple_and_assert(
942 "test/importing/including-imports/app_release.kts",
943 &[
944 (
945 "fixtures/fe/importing/including-imports/ui.fml.yaml",
946 "none",
947 ),
948 (
949 "fixtures/fe/importing/including-imports/app.fml.yaml",
950 "release",
951 ),
952 ],
953 )?;
954 Ok(())
955 }
956
957 #[test]
958 fn regression_test_concurrent_access_of_feature_holder_kts() -> Result<()> {
959 generate_and_assert(
960 "test/threadsafe_feature_holder.kts",
961 "fixtures/fe/browser.yaml",
962 "release",
963 false,
964 )?;
965 Ok(())
966 }
967
968 #[test]
969 fn test_with_coenrolled_features_and_imports_kts() -> Result<()> {
970 generate_multiple_and_assert(
971 "test/allow_coenrolling.kts",
972 &[
973 ("fixtures/fe/importing/coenrolling/app.fml.yaml", "release"),
974 ("fixtures/fe/importing/coenrolling/ui.fml.yaml", "release"),
975 ],
976 )?;
977 Ok(())
978 }
979
980 #[test]
981 fn test_with_preference_overrides_kt() -> Result<()> {
982 generate_multiple_and_assert(
983 "test/pref_overrides.kts",
984 &[("fixtures/fe/pref_overrides.fml.yaml", "debug")],
985 )?;
986 Ok(())
987 }
988}
989
990#[cfg(all(
991 test,
992 feature = "swift-tests",
993 not(feature = "all-features-workaround")
994))]
995mod swift_tests {
996 use super::{
997 test::{generate_and_assert, generate_multiple_and_assert},
998 *,
999 };
1000
1001 #[test]
1002 fn test_with_app_menu_swift_from_ir() -> Result<()> {
1003 generate_and_assert(
1004 "test/app_menu.swift",
1005 "fixtures/ir/app_menu.json",
1006 "release",
1007 true,
1008 )?;
1009 Ok(())
1010 }
1011
1012 #[test]
1013 fn test_with_objects_swift_from_ir() -> Result<()> {
1014 generate_and_assert(
1015 "test/with_objects.swift",
1016 "fixtures/ir/with_objects.json",
1017 "release",
1018 true,
1019 )?;
1020 Ok(())
1021 }
1022
1023 #[test]
1024 fn test_with_bundled_resources_swift() -> Result<()> {
1025 generate_and_assert(
1026 "test/bundled_resources.swift",
1027 "fixtures/fe/bundled_resouces.yaml",
1028 "testing",
1029 false,
1030 )?;
1031 Ok(())
1032 }
1033
1034 #[test]
1035 fn test_with_full_fenix_release_swift() -> Result<()> {
1036 generate_and_assert(
1037 "test/fenix_release.swift",
1038 "fixtures/fe/browser.yaml",
1039 "release",
1040 false,
1041 )?;
1042 Ok(())
1043 }
1044
1045 #[test]
1046 fn test_with_full_fenix_nightly_swift() -> Result<()> {
1047 generate_and_assert(
1048 "test/fenix_nightly.swift",
1049 "fixtures/fe/browser.yaml",
1050 "nightly",
1051 false,
1052 )?;
1053 Ok(())
1054 }
1055
1056 #[test]
1057 fn test_with_full_firefox_swift() -> Result<()> {
1058 generate_and_assert(
1059 "test/firefox_ios_release.swift",
1060 "fixtures/fe/including/ios.yaml",
1061 "release",
1062 false,
1063 )?;
1064 Ok(())
1065 }
1066
1067 #[test]
1068 fn test_importing_simple_swift() -> Result<()> {
1069 generate_multiple_and_assert(
1070 "test/importing/simple/app_debug.swift",
1071 &[
1072 ("fixtures/fe/importing/simple/app.yaml", "debug"),
1073 ("fixtures/fe/importing/simple/lib.yaml", "debug"),
1074 ],
1075 )?;
1076 Ok(())
1077 }
1078
1079 #[test]
1080 fn test_importing_override_defaults_swift() -> Result<()> {
1081 generate_multiple_and_assert(
1082 "test/importing/overrides/app_debug.swift",
1083 &[
1084 ("fixtures/fe/importing/overrides/app.fml.yaml", "debug"),
1085 ("fixtures/fe/importing/overrides/lib.fml.yaml", "debug"),
1086 ],
1087 )?;
1088 Ok(())
1089 }
1090 #[test]
1091 fn test_importing_diamond_overrides_swift() -> Result<()> {
1092 generate_multiple_and_assert(
1096 "test/importing/diamond/00-app.swift",
1097 &[
1098 ("fixtures/fe/importing/diamond/00-app.yaml", "debug"),
1099 ("fixtures/fe/importing/diamond/01-lib.yaml", "debug"),
1100 ("fixtures/fe/importing/diamond/02-sublib.yaml", "debug"),
1101 ],
1102 )?;
1103 Ok(())
1104 }
1105
1106 #[test]
1107 fn test_importing_including_imports_swift() -> Result<()> {
1108 generate_multiple_and_assert(
1109 "test/importing/including-imports/app_release.swift",
1110 &[
1111 (
1112 "fixtures/fe/importing/including-imports/ui.fml.yaml",
1113 "none",
1114 ),
1115 (
1116 "fixtures/fe/importing/including-imports/app.fml.yaml",
1117 "release",
1118 ),
1119 ],
1120 )?;
1121 Ok(())
1122 }
1123 #[test]
1124 fn regression_test_concurrent_access_of_feature_holder_swift() -> Result<()> {
1125 generate_and_assert(
1126 "test/threadsafe_feature_holder.swift",
1127 "fixtures/fe/browser.yaml",
1128 "release",
1129 false,
1130 )?;
1131 Ok(())
1132 }
1133
1134 #[test]
1135 fn test_with_coenrolled_features_and_imports_swift() -> Result<()> {
1136 generate_multiple_and_assert(
1137 "test/allow_coenrolling.swift",
1138 &[
1139 ("fixtures/fe/importing/coenrolling/app.fml.yaml", "release"),
1140 ("fixtures/fe/importing/coenrolling/ui.fml.yaml", "release"),
1141 ],
1142 )?;
1143 Ok(())
1144 }
1145
1146 #[test]
1147 fn test_with_preference_overrides_swift() -> Result<()> {
1148 generate_multiple_and_assert(
1149 "test/pref_overrides.swift",
1150 &[("fixtures/fe/pref_overrides.fml.yaml", "debug")],
1151 )?;
1152 Ok(())
1153 }
1154}