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