1use clap::{Parser, Subcommand, ValueEnum};
4use std::path::PathBuf;
5
6#[derive(Parser, Debug)]
8#[command(name = "probador")]
9#[command(author, version, about, long_about = None)]
10#[command(propagate_version = true)]
11pub struct Cli {
12 #[arg(short, long, action = clap::ArgAction::Count, global = true)]
14 pub verbose: u8,
15
16 #[arg(short, long, global = true)]
18 pub quiet: bool,
19
20 #[arg(long, default_value = "auto", global = true)]
22 pub color: ColorArg,
23
24 #[command(subcommand)]
26 pub command: Commands,
27}
28
29#[derive(Subcommand, Debug)]
31pub enum Commands {
32 Test(TestArgs),
34
35 Record(RecordArgs),
37
38 Report(ReportArgs),
40
41 Coverage(CoverageArgs),
43
44 Init(InitArgs),
46
47 Config(ConfigArgs),
49
50 Serve(ServeArgs),
52
53 Build(BuildArgs),
55
56 Watch(WatchArgs),
58
59 Playbook(PlaybookArgs),
61
62 Comply(ComplyArgs),
76
77 AvSync(AvSyncArgs),
82
83 Audio(AudioArgs),
88
89 Video(VideoArgs),
94
95 Animation(AnimationArgs),
100
101 Stress(StressArgs),
110
111 Llm(LlmArgs),
118}
119
120#[derive(Parser, Debug)]
122pub struct AvSyncArgs {
123 #[command(subcommand)]
125 pub subcommand: AvSyncSubcommand,
126}
127
128#[derive(Subcommand, Debug)]
130pub enum AvSyncSubcommand {
131 Check(AvSyncCheckArgs),
133
134 Report(AvSyncReportArgs),
136}
137
138#[derive(Parser, Debug)]
140pub struct AvSyncCheckArgs {
141 pub video: PathBuf,
143
144 #[arg(long)]
146 pub edl: Option<PathBuf>,
147
148 #[arg(long, default_value = "20")]
150 pub tolerance_ms: f64,
151
152 #[arg(long, default_value = "text")]
154 pub format: AvSyncOutputFormat,
155
156 #[arg(long)]
158 pub detailed: bool,
159}
160
161#[derive(Parser, Debug)]
163pub struct AvSyncReportArgs {
164 pub dir: PathBuf,
166
167 #[arg(long, default_value = "20")]
169 pub tolerance_ms: f64,
170
171 #[arg(long, default_value = "text")]
173 pub format: AvSyncOutputFormat,
174
175 #[arg(short, long)]
177 pub output: Option<PathBuf>,
178}
179
180#[derive(ValueEnum, Clone, Debug, Default)]
182pub enum AvSyncOutputFormat {
183 #[default]
185 Text,
186 Json,
188}
189
190#[derive(Parser, Debug)]
192pub struct AudioArgs {
193 #[command(subcommand)]
195 pub subcommand: AudioSubcommand,
196}
197
198#[derive(Subcommand, Debug)]
200pub enum AudioSubcommand {
201 Check(AudioCheckArgs),
203}
204
205#[derive(Parser, Debug)]
207pub struct AudioCheckArgs {
208 pub video: PathBuf,
210
211 #[arg(long, default_value = "48000")]
213 pub sample_rate: u32,
214
215 #[arg(long, default_value = "-40", allow_hyphen_values = true)]
217 pub min_rms_dbfs: f64,
218
219 #[arg(long, default_value = "-0.1", allow_hyphen_values = true)]
221 pub max_peak_dbfs: f64,
222
223 #[arg(long, default_value = "true")]
225 pub no_clipping: bool,
226
227 #[arg(long, default_value = "text")]
229 pub format: OutputFormat,
230}
231
232#[derive(Parser, Debug)]
234pub struct VideoArgs {
235 #[command(subcommand)]
237 pub subcommand: VideoSubcommand,
238}
239
240#[derive(Subcommand, Debug)]
242pub enum VideoSubcommand {
243 Check(VideoCheckArgs),
245}
246
247#[derive(Parser, Debug)]
249pub struct VideoCheckArgs {
250 pub video: PathBuf,
252
253 #[arg(long)]
255 pub width: Option<u32>,
256
257 #[arg(long)]
259 pub height: Option<u32>,
260
261 #[arg(long)]
263 pub fps: Option<f64>,
264
265 #[arg(long)]
267 pub codec: Option<String>,
268
269 #[arg(long)]
271 pub min_duration: Option<f64>,
272
273 #[arg(long)]
275 pub max_duration: Option<f64>,
276
277 #[arg(long)]
279 pub require_audio: bool,
280
281 #[arg(long, default_value = "text")]
283 pub format: OutputFormat,
284}
285
286#[derive(Parser, Debug)]
288pub struct AnimationArgs {
289 #[command(subcommand)]
291 pub subcommand: AnimationSubcommand,
292}
293
294#[derive(Subcommand, Debug)]
296pub enum AnimationSubcommand {
297 Check(AnimationCheckArgs),
299}
300
301#[derive(Parser, Debug)]
303pub struct AnimationCheckArgs {
304 pub timeline: PathBuf,
306
307 pub observed: PathBuf,
309
310 #[arg(long, default_value = "20")]
312 pub tolerance_ms: f64,
313
314 #[arg(long, default_value = "text")]
316 pub format: OutputFormat,
317}
318
319#[derive(ValueEnum, Clone, Debug, Default)]
321pub enum OutputFormat {
322 #[default]
324 Text,
325 Json,
327}
328
329#[derive(Parser, Debug)]
331#[allow(clippy::struct_excessive_bools)]
332pub struct StressArgs {
333 #[arg(long, default_value = "atomics")]
335 pub mode: String,
336
337 #[arg(short, long, default_value = "30")]
339 pub duration: u64,
340
341 #[arg(short, long, default_value = "4")]
343 pub concurrency: u32,
344
345 #[arg(short, long, default_value = "text")]
347 pub output: String,
348
349 #[arg(long)]
351 pub atomics: bool,
352
353 #[arg(long)]
355 pub worker_msg: bool,
356
357 #[arg(long)]
359 pub render: bool,
360
361 #[arg(long)]
363 pub trace: bool,
364
365 #[arg(long)]
367 pub full: bool,
368}
369
370impl StressArgs {
371 #[must_use]
373 pub fn get_mode(&self) -> String {
374 if self.atomics {
375 "atomics".to_string()
376 } else if self.worker_msg {
377 "worker-msg".to_string()
378 } else if self.render {
379 "render".to_string()
380 } else if self.trace {
381 "trace".to_string()
382 } else if self.full {
383 "full".to_string()
384 } else {
385 self.mode.clone()
386 }
387 }
388}
389
390#[derive(Parser, Debug)]
392pub struct LlmArgs {
393 #[command(subcommand)]
395 pub subcommand: LlmSubcommand,
396}
397
398#[derive(Subcommand, Debug)]
400pub enum LlmSubcommand {
401 Test(LlmTestArgs),
403 Load(LlmLoadArgs),
405 Bench(LlmBenchArgs),
407 Report(LlmReportArgs),
409 Experiment(ExperimentArgs),
411 DataAudit(DataAuditArgs),
413 Sweep(LlmSweepArgs),
415 GenDataset(LlmGenDatasetArgs),
417 Score(LlmScoreArgs),
419}
420
421#[derive(Parser, Debug)]
423pub struct LlmTestArgs {
424 #[arg(short, long)]
426 pub config: PathBuf,
427
428 #[arg(short, long)]
430 pub url: String,
431
432 #[arg(short, long, default_value = "default")]
434 pub model: String,
435
436 #[arg(long, default_value = "unknown")]
438 pub runtime_name: String,
439
440 #[arg(short, long)]
442 pub output: Option<PathBuf>,
443}
444
445#[derive(Parser, Debug)]
447pub struct LlmLoadArgs {
448 #[arg(short, long)]
450 pub url: String,
451
452 #[arg(short, long, default_value = "default")]
454 pub model: String,
455
456 #[arg(short, long, default_value = "4")]
458 pub concurrency: usize,
459
460 #[arg(short, long, default_value = "30s")]
462 pub duration: String,
463
464 #[arg(long, default_value = "unknown")]
466 pub runtime_name: String,
467
468 #[arg(long)]
470 pub prompt_profile: Option<String>,
471
472 #[arg(long)]
474 pub prompt_file: Option<PathBuf>,
475
476 #[arg(long, default_value = "0s")]
478 pub warmup: String,
479
480 #[arg(short, long)]
482 pub output: Option<PathBuf>,
483
484 #[arg(long, default_value = "true", action = clap::ArgAction::Set)]
487 pub stream: bool,
488
489 #[arg(long)]
491 pub rate: Option<f64>,
492
493 #[arg(long, default_value = "poisson")]
495 pub rate_distribution: String,
496
497 #[arg(long)]
500 pub num_layers: Option<u32>,
501
502 #[arg(long, default_value = "none")]
504 pub validate: String,
505
506 #[arg(long)]
508 pub fail_on_quality: Option<f64>,
509
510 #[arg(long, default_value = "5.0")]
512 pub spike_threshold: f64,
513
514 #[arg(long)]
516 pub gpu_telemetry: bool,
517
518 #[arg(long, default_value = "1s")]
520 pub gpu_poll_interval: String,
521
522 #[arg(long)]
524 pub expected_clock_mhz: Option<u32>,
525
526 #[arg(long)]
528 pub skip_health_check: bool,
529
530 #[arg(long)]
532 pub dataset: Option<PathBuf>,
533
534 #[arg(long)]
536 pub max_tokens: Option<u32>,
537
538 #[arg(long)]
542 pub max_tokens_distribution: Option<String>,
543}
544
545#[derive(Parser, Debug)]
547pub struct LlmBenchArgs {
548 #[arg(short, long)]
550 pub url: String,
551
552 #[arg(short, long, default_value = "default")]
554 pub model: String,
555
556 #[arg(long)]
558 pub start: Option<String>,
559
560 #[arg(long, default_value = "120s")]
562 pub health_timeout: String,
563
564 #[arg(long, default_value = "medium")]
566 pub prompt_profile: String,
567
568 #[arg(long)]
570 pub prompt_file: Option<PathBuf>,
571
572 #[arg(long, default_value = "10s")]
574 pub warmup: String,
575
576 #[arg(short, long, default_value = "60s")]
578 pub duration: String,
579
580 #[arg(short, long, default_value = "1")]
582 pub concurrency: usize,
583
584 #[arg(long, default_value = "3")]
586 pub runs: usize,
587
588 #[arg(long, default_value = "5s")]
590 pub cooldown: String,
591
592 #[arg(long)]
594 pub baseline: Option<PathBuf>,
595
596 #[arg(long)]
598 pub fail_on_regression: Option<f64>,
599
600 #[arg(long, default_value = "unknown")]
602 pub runtime_name: String,
603
604 #[arg(short, long)]
606 pub output: Option<PathBuf>,
607
608 #[arg(long, default_value = "true", action = clap::ArgAction::Set)]
611 pub stream: bool,
612
613 #[arg(long)]
615 pub trace_level: Option<String>,
616
617 #[arg(long)]
620 pub num_layers: Option<u32>,
621}
622
623#[derive(Parser, Debug)]
625pub struct LlmReportArgs {
626 #[arg(short, long)]
628 pub results: PathBuf,
629
630 #[arg(short, long, default_value = "performance.md")]
632 pub output: PathBuf,
633
634 #[arg(long)]
636 pub update_readme: Option<PathBuf>,
637}
638
639#[derive(Parser, Debug)]
641pub struct LlmScoreArgs {
642 #[arg(short, long)]
644 pub results: PathBuf,
645
646 #[arg(short, long)]
648 pub concurrency: Option<usize>,
649
650 #[arg(long)]
652 pub platform: Option<String>,
653
654 #[arg(short, long)]
656 pub output: Option<PathBuf>,
657
658 #[arg(long, default_value = "table")]
660 pub format: String,
661
662 #[arg(long)]
664 pub fail_on_grade: Option<String>,
665
666 #[arg(long)]
668 pub by_layer: bool,
669
670 #[arg(long)]
672 pub by_profile: bool,
673
674 #[arg(long)]
676 pub by_correctness: bool,
677
678 #[arg(long)]
680 pub by_output_length: bool,
681
682 #[arg(long)]
684 pub by_memory: bool,
685
686 #[arg(long)]
688 pub by_cold_start: bool,
689
690 #[arg(long)]
692 pub by_power: bool,
693
694 #[arg(long)]
696 pub by_scaling: bool,
697}
698
699#[derive(Parser, Debug)]
701pub struct ExperimentArgs {
702 #[command(subcommand)]
704 pub subcommand: ExperimentSubcommand,
705}
706
707#[derive(Subcommand, Debug)]
709pub enum ExperimentSubcommand {
710 Init(ExperimentInitArgs),
712 Status(ExperimentStatusArgs),
714 Compare(ExperimentCompareArgs),
716}
717
718#[derive(Parser, Debug)]
720pub struct ExperimentInitArgs {
721 pub name: String,
723
724 #[arg(short, long)]
726 pub description: Option<String>,
727
728 #[arg(long)]
730 pub max_gpu_hours: Option<f64>,
731
732 #[arg(long)]
734 pub max_cost_usd: Option<f64>,
735
736 #[arg(long, default_value = "3.50")]
738 pub cost_per_gpu_hour: f64,
739
740 #[arg(short, long, default_value = "experiment.json")]
742 pub output: PathBuf,
743}
744
745#[derive(Parser, Debug)]
747pub struct ExperimentStatusArgs {
748 #[arg(short, long, default_value = "experiment.json")]
750 pub file: PathBuf,
751}
752
753#[derive(Parser, Debug)]
755pub struct ExperimentCompareArgs {
756 #[arg(short = 'f', long, default_value = "experiment.json")]
758 pub file: PathBuf,
759
760 pub run_a: String,
762
763 pub run_b: String,
765
766 #[arg(short, long, default_value = "eval_loss")]
768 pub metric: String,
769
770 #[arg(long)]
772 pub lower_is_better: bool,
773}
774
775#[derive(Parser, Debug)]
777pub struct DataAuditArgs {
778 pub file: PathBuf,
780
781 #[arg(long, default_value = "3.0")]
783 pub max_imbalance: f64,
784}
785
786#[derive(Parser, Debug)]
788pub struct LlmSweepArgs {
789 #[arg(short, long)]
791 pub url: String,
792
793 #[arg(short, long, default_value = "default")]
795 pub model: String,
796
797 #[arg(long, default_value = "1,2,4,8,16")]
799 pub concurrency_levels: String,
800
801 #[arg(short, long, default_value = "30s")]
803 pub duration: String,
804
805 #[arg(long, default_value = "5s")]
807 pub warmup: String,
808
809 #[arg(long, default_value = "true", action = clap::ArgAction::Set)]
811 pub stream: bool,
812
813 #[arg(long, default_value = "unknown")]
815 pub runtime_name: String,
816
817 #[arg(long, default_value = "2.0")]
819 pub saturation_threshold: f64,
820
821 #[arg(long, default_value = "true", action = clap::ArgAction::Set)]
823 pub early_stop: bool,
824
825 #[arg(long)]
827 pub prompt_profile: Option<String>,
828
829 #[arg(long)]
831 pub prompt_file: Option<PathBuf>,
832
833 #[arg(short, long)]
835 pub output: Option<PathBuf>,
836
837 #[arg(long)]
839 pub num_layers: Option<u32>,
840}
841
842#[derive(Parser, Debug)]
844pub struct LlmGenDatasetArgs {
845 #[arg(long, default_value = "lognormal")]
847 pub distribution: String,
848
849 #[arg(long, default_value = "128")]
851 pub input_mean: f64,
852
853 #[arg(long, default_value = "64")]
855 pub input_stddev: f64,
856
857 #[arg(long, default_value = "128")]
859 pub output_mean: f64,
860
861 #[arg(long, default_value = "96")]
863 pub output_stddev: f64,
864
865 #[arg(long, default_value = "1000")]
867 pub count: usize,
868
869 #[arg(short, long, default_value = "dataset.jsonl")]
871 pub output: PathBuf,
872}
873
874#[derive(Parser, Debug)]
876#[allow(clippy::struct_excessive_bools)]
877pub struct TestArgs {
878 #[arg(short, long)]
880 pub filter: Option<String>,
881
882 #[arg(short = 'j', long, default_value = "0")]
884 pub parallel: usize,
885
886 #[arg(long)]
888 pub coverage: bool,
889
890 #[arg(long)]
892 pub mutants: bool,
893
894 #[arg(long)]
896 pub fail_fast: bool,
897
898 #[arg(short, long)]
900 pub watch: bool,
901
902 #[arg(long, default_value = "30000")]
904 pub timeout: u64,
905
906 #[arg(short, long, default_value = "target/probar")]
908 pub output: PathBuf,
909
910 #[arg(long)]
914 pub skip_compile: bool,
915}
916
917#[derive(Parser, Debug)]
919pub struct RecordArgs {
920 pub test: String,
922
923 #[arg(short, long, default_value = "gif")]
925 pub format: RecordFormat,
926
927 #[arg(short, long)]
929 pub output: Option<PathBuf>,
930
931 #[arg(long, default_value = "10")]
933 pub fps: u8,
934
935 #[arg(long, default_value = "80")]
937 pub quality: u8,
938}
939
940#[derive(ValueEnum, Clone, Debug, Default)]
942pub enum RecordFormat {
943 #[default]
945 Gif,
946 Png,
948 Svg,
950 Mp4,
952}
953
954#[derive(Parser, Debug)]
956pub struct ReportArgs {
957 #[arg(short, long, default_value = "html")]
959 pub format: ReportFormat,
960
961 #[arg(short, long, default_value = "target/probar/reports")]
963 pub output: PathBuf,
964
965 #[arg(long)]
967 pub open: bool,
968}
969
970#[derive(ValueEnum, Clone, Debug, Default)]
972pub enum ReportFormat {
973 #[default]
975 Html,
976 Junit,
978 Lcov,
980 Cobertura,
982 Json,
984}
985
986#[derive(Parser, Debug)]
988pub struct CoverageArgs {
989 #[arg(long)]
991 pub png: Option<PathBuf>,
992
993 #[arg(long)]
995 pub json: Option<PathBuf>,
996
997 #[arg(long, default_value = "viridis")]
999 pub palette: PaletteArg,
1000
1001 #[arg(long)]
1003 pub legend: bool,
1004
1005 #[arg(long)]
1007 pub gaps: bool,
1008
1009 #[arg(long)]
1011 pub title: Option<String>,
1012
1013 #[arg(long, default_value = "800")]
1015 pub width: u32,
1016
1017 #[arg(long, default_value = "600")]
1019 pub height: u32,
1020
1021 #[arg(short, long)]
1023 pub input: Option<PathBuf>,
1024}
1025
1026#[derive(ValueEnum, Clone, Debug, Default)]
1028pub enum PaletteArg {
1029 #[default]
1031 Viridis,
1032 Magma,
1034 Heat,
1036}
1037
1038#[derive(Parser, Debug)]
1040pub struct InitArgs {
1041 #[arg(default_value = ".")]
1043 pub path: PathBuf,
1044
1045 #[arg(short, long)]
1047 pub force: bool,
1048}
1049
1050#[derive(Parser, Debug)]
1052pub struct ConfigArgs {
1053 #[arg(long)]
1055 pub show: bool,
1056
1057 #[arg(long)]
1059 pub set: Option<String>,
1060
1061 #[arg(long)]
1063 pub reset: bool,
1064}
1065
1066#[derive(Parser, Debug)]
1068#[allow(clippy::struct_excessive_bools)]
1069pub struct ServeArgs {
1070 #[command(subcommand)]
1072 pub subcommand: Option<ServeSubcommand>,
1073
1074 #[arg(short = 'd', long = "dir", default_value = ".")]
1076 pub directory: PathBuf,
1077
1078 #[arg(short, long, default_value = "8080")]
1080 pub port: u16,
1081
1082 #[arg(long, default_value = "8081")]
1084 pub ws_port: u16,
1085
1086 #[arg(long)]
1088 pub open: bool,
1089
1090 #[arg(long)]
1092 pub cors: bool,
1093
1094 #[arg(long)]
1100 pub cross_origin_isolated: bool,
1101
1102 #[arg(long)]
1104 pub debug: bool,
1105
1106 #[arg(long)]
1108 pub lint: bool,
1109
1110 #[arg(long, default_value = "true")]
1112 pub watch: bool,
1113
1114 #[arg(long)]
1119 pub validate: bool,
1120
1121 #[arg(long)]
1123 pub monitor: bool,
1124
1125 #[arg(long, value_name = "DIR")]
1129 pub exclude: Vec<String>,
1130}
1131
1132#[derive(Subcommand, Debug, Clone)]
1134pub enum ServeSubcommand {
1135 Tree(TreeArgs),
1137
1138 Viz(VizArgs),
1140
1141 Score(ScoreArgs),
1143}
1144
1145#[derive(Parser, Debug, Clone)]
1147pub struct TreeArgs {
1148 #[arg(default_value = ".")]
1150 pub path: PathBuf,
1151
1152 #[arg(long)]
1154 pub depth: Option<usize>,
1155
1156 #[arg(long)]
1158 pub filter: Option<String>,
1159
1160 #[arg(long, default_value = "true")]
1162 pub sizes: bool,
1163
1164 #[arg(long, default_value = "true")]
1166 pub mime_types: bool,
1167}
1168
1169#[derive(Parser, Debug, Clone)]
1171pub struct VizArgs {
1172 #[arg(default_value = ".")]
1174 pub path: PathBuf,
1175
1176 #[arg(short, long, default_value = "8080")]
1178 pub port: u16,
1179}
1180
1181#[derive(Parser, Debug, Clone)]
1183pub struct ScoreArgs {
1184 #[arg(default_value = ".")]
1186 pub path: PathBuf,
1187
1188 #[arg(long)]
1190 pub min: Option<u32>,
1191
1192 #[arg(long, default_value = "text")]
1194 pub format: ScoreOutputFormat,
1195
1196 #[arg(long)]
1198 pub report: Option<PathBuf>,
1199
1200 #[arg(long)]
1202 pub detailed: bool,
1203
1204 #[arg(long)]
1206 pub history: Option<PathBuf>,
1207
1208 #[arg(long)]
1210 pub trend: bool,
1211
1212 #[arg(long)]
1217 pub live: bool,
1218
1219 #[arg(long, default_value = "0")]
1221 pub port: u16,
1222}
1223
1224#[derive(ValueEnum, Clone, Debug, Default)]
1226pub enum ScoreOutputFormat {
1227 #[default]
1229 Text,
1230 Json,
1232}
1233
1234#[derive(Parser, Debug)]
1236pub struct BuildArgs {
1237 #[arg(default_value = ".")]
1239 pub path: PathBuf,
1240
1241 #[arg(short, long, default_value = "web")]
1243 pub target: WasmTarget,
1244
1245 #[arg(long)]
1247 pub release: bool,
1248
1249 #[arg(short, long)]
1251 pub out_dir: Option<PathBuf>,
1252
1253 #[arg(long)]
1255 pub profiling: bool,
1256
1257 #[arg(long)]
1265 pub bricks: Option<PathBuf>,
1266
1267 #[arg(long, default_value = "app")]
1269 pub app_name: String,
1270
1271 #[arg(long, default_value = "./pkg/app.js")]
1273 pub wasm_module: String,
1274
1275 #[arg(long)]
1277 pub model_path: Option<String>,
1278
1279 #[arg(long)]
1281 pub title: Option<String>,
1282
1283 #[arg(long)]
1285 pub verify: bool,
1286}
1287
1288#[derive(ValueEnum, Clone, Debug, Default)]
1290pub enum WasmTarget {
1291 #[default]
1293 Web,
1294 Bundler,
1296 Nodejs,
1298 NoModules,
1300}
1301
1302impl WasmTarget {
1303 #[must_use]
1305 pub const fn as_str(&self) -> &'static str {
1306 match self {
1307 Self::Web => "web",
1308 Self::Bundler => "bundler",
1309 Self::Nodejs => "nodejs",
1310 Self::NoModules => "no-modules",
1311 }
1312 }
1313}
1314
1315#[derive(Parser, Debug)]
1317pub struct WatchArgs {
1318 #[arg(default_value = ".")]
1320 pub path: PathBuf,
1321
1322 #[arg(long)]
1324 pub serve: bool,
1325
1326 #[arg(short, long, default_value = "8080")]
1328 pub port: u16,
1329
1330 #[arg(long, default_value = "8081")]
1332 pub ws_port: u16,
1333
1334 #[arg(short, long, default_value = "web")]
1336 pub target: WasmTarget,
1337
1338 #[arg(long)]
1340 pub release: bool,
1341
1342 #[arg(long, default_value = "500")]
1344 pub debounce: u64,
1345}
1346
1347#[derive(Parser, Debug)]
1349#[allow(clippy::struct_excessive_bools)]
1350pub struct PlaybookArgs {
1351 #[arg(required = true)]
1353 pub files: Vec<PathBuf>,
1354
1355 #[arg(long)]
1357 pub validate: bool,
1358
1359 #[arg(long, value_enum)]
1361 pub export: Option<DiagramFormat>,
1362
1363 #[arg(long)]
1365 pub export_output: Option<PathBuf>,
1366
1367 #[arg(long)]
1369 pub mutate: bool,
1370
1371 #[arg(long, value_delimiter = ',')]
1373 pub mutation_classes: Option<Vec<String>>,
1374
1375 #[arg(long)]
1377 pub fail_fast: bool,
1378
1379 #[arg(long)]
1381 pub continue_on_error: bool,
1382
1383 #[arg(short, long, default_value = "text")]
1385 pub format: PlaybookOutputFormat,
1386
1387 #[arg(short, long, default_value = "target/probar/playbooks")]
1389 pub output: PathBuf,
1390}
1391
1392#[derive(ValueEnum, Clone, Debug)]
1394pub enum DiagramFormat {
1395 Dot,
1397 Svg,
1399}
1400
1401#[derive(ValueEnum, Clone, Debug, Default)]
1403pub enum PlaybookOutputFormat {
1404 #[default]
1406 Text,
1407 Json,
1409 Junit,
1411}
1412
1413#[derive(Parser, Debug)]
1415#[allow(clippy::struct_excessive_bools)]
1416pub struct ComplyArgs {
1417 #[command(subcommand)]
1419 pub subcommand: Option<ComplySubcommand>,
1420
1421 #[arg(default_value = ".")]
1423 pub path: PathBuf,
1424
1425 #[arg(long, value_delimiter = ',')]
1427 pub checks: Option<Vec<String>>,
1428
1429 #[arg(long)]
1431 pub fail_fast: bool,
1432
1433 #[arg(long, default_value = "text")]
1435 pub format: ComplyOutputFormat,
1436
1437 #[arg(long, default_value = "5242880")]
1439 pub max_wasm_size: usize,
1440
1441 #[arg(long)]
1443 pub strict: bool,
1444
1445 #[arg(long)]
1447 pub report: Option<PathBuf>,
1448
1449 #[arg(long)]
1451 pub detailed: bool,
1452}
1453
1454#[derive(Subcommand, Debug, Clone)]
1456pub enum ComplySubcommand {
1457 Check(ComplyCheckArgs),
1459
1460 Migrate(ComplyMigrateArgs),
1462
1463 Diff(ComplyDiffArgs),
1465
1466 Enforce(ComplyEnforceArgs),
1468
1469 Report(ComplyReportArgs),
1471}
1472
1473#[derive(Parser, Debug, Clone)]
1475#[allow(clippy::struct_excessive_bools)]
1476pub struct ComplyCheckArgs {
1477 #[arg(default_value = ".")]
1479 pub path: PathBuf,
1480
1481 #[arg(long)]
1483 pub strict: bool,
1484
1485 #[arg(long, default_value = "text")]
1487 pub format: ComplyOutputFormat,
1488
1489 #[arg(long, value_delimiter = ',')]
1491 pub checks: Option<Vec<String>>,
1492
1493 #[arg(long)]
1495 pub detailed: bool,
1496}
1497
1498#[derive(Parser, Debug, Clone)]
1500pub struct ComplyMigrateArgs {
1501 #[arg(default_value = ".")]
1503 pub path: PathBuf,
1504
1505 #[arg(long)]
1507 pub version: Option<String>,
1508
1509 #[arg(long)]
1511 pub dry_run: bool,
1512
1513 #[arg(long)]
1515 pub force: bool,
1516}
1517
1518#[derive(Parser, Debug, Clone)]
1520pub struct ComplyDiffArgs {
1521 #[arg(long)]
1523 pub from: Option<String>,
1524
1525 #[arg(long)]
1527 pub to: Option<String>,
1528
1529 #[arg(long)]
1531 pub breaking_only: bool,
1532}
1533
1534#[derive(Parser, Debug, Clone)]
1536pub struct ComplyEnforceArgs {
1537 #[arg(default_value = ".")]
1539 pub path: PathBuf,
1540
1541 #[arg(long)]
1543 pub yes: bool,
1544
1545 #[arg(long)]
1547 pub disable: bool,
1548}
1549
1550#[derive(Parser, Debug, Clone)]
1552pub struct ComplyReportArgs {
1553 #[arg(default_value = ".")]
1555 pub path: PathBuf,
1556
1557 #[arg(long, default_value = "text")]
1559 pub format: ComplyReportFormat,
1560
1561 #[arg(long)]
1563 pub output: Option<PathBuf>,
1564}
1565
1566#[derive(ValueEnum, Clone, Debug, Default)]
1568pub enum ComplyReportFormat {
1569 #[default]
1571 Text,
1572 Json,
1574 Markdown,
1576 Html,
1578}
1579
1580#[derive(ValueEnum, Clone, Debug, Default)]
1582pub enum ComplyOutputFormat {
1583 #[default]
1585 Text,
1586 Json,
1588 Junit,
1590}
1591
1592#[derive(ValueEnum, Clone, Debug, Default)]
1594pub enum ColorArg {
1595 #[default]
1597 Auto,
1598 Always,
1600 Never,
1602}
1603
1604impl From<ColorArg> for crate::config::ColorChoice {
1605 fn from(arg: ColorArg) -> Self {
1606 match arg {
1607 ColorArg::Auto => Self::Auto,
1608 ColorArg::Always => Self::Always,
1609 ColorArg::Never => Self::Never,
1610 }
1611 }
1612}
1613
1614#[cfg(test)]
1615#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
1616mod tests {
1617 use super::*;
1618
1619 mod cli_tests {
1620 use super::*;
1621
1622 #[test]
1623 fn test_parse_test_command() {
1624 let cli = Cli::parse_from(["probar", "test"]);
1625 assert!(matches!(cli.command, Commands::Test(_)));
1626 }
1627
1628 #[test]
1629 fn test_parse_test_with_filter() {
1630 let cli = Cli::parse_from(["probar", "test", "--filter", "game::*"]);
1631 if let Commands::Test(args) = cli.command {
1632 assert_eq!(args.filter, Some("game::*".to_string()));
1633 } else {
1634 panic!("expected Test command");
1635 }
1636 }
1637
1638 #[test]
1639 fn test_parse_test_with_parallel() {
1640 let cli = Cli::parse_from(["probar", "test", "-j", "4"]);
1641 if let Commands::Test(args) = cli.command {
1642 assert_eq!(args.parallel, 4);
1643 } else {
1644 panic!("expected Test command");
1645 }
1646 }
1647
1648 #[test]
1649 fn test_parse_test_with_coverage() {
1650 let cli = Cli::parse_from(["probar", "test", "--coverage"]);
1651 if let Commands::Test(args) = cli.command {
1652 assert!(args.coverage);
1653 } else {
1654 panic!("expected Test command");
1655 }
1656 }
1657
1658 #[test]
1659 fn test_parse_test_with_fail_fast() {
1660 let cli = Cli::parse_from(["probar", "test", "--fail-fast"]);
1661 if let Commands::Test(args) = cli.command {
1662 assert!(args.fail_fast);
1663 } else {
1664 panic!("expected Test command");
1665 }
1666 }
1667
1668 #[test]
1669 fn test_parse_record_command() {
1670 let cli = Cli::parse_from(["probar", "record", "test_login"]);
1671 if let Commands::Record(args) = cli.command {
1672 assert_eq!(args.test, "test_login");
1673 } else {
1674 panic!("expected Record command");
1675 }
1676 }
1677
1678 #[test]
1679 fn test_parse_record_with_format() {
1680 let cli = Cli::parse_from(["probar", "record", "test_login", "--format", "png"]);
1681 if let Commands::Record(args) = cli.command {
1682 assert!(matches!(args.format, RecordFormat::Png));
1683 } else {
1684 panic!("expected Record command");
1685 }
1686 }
1687
1688 #[test]
1689 fn test_parse_report_command() {
1690 let cli = Cli::parse_from(["probar", "report"]);
1691 assert!(matches!(cli.command, Commands::Report(_)));
1692 }
1693
1694 #[test]
1695 fn test_parse_report_with_format() {
1696 let cli = Cli::parse_from(["probar", "report", "--format", "lcov"]);
1697 if let Commands::Report(args) = cli.command {
1698 assert!(matches!(args.format, ReportFormat::Lcov));
1699 } else {
1700 panic!("expected Report command");
1701 }
1702 }
1703
1704 #[test]
1705 fn test_parse_init_command() {
1706 let cli = Cli::parse_from(["probar", "init"]);
1707 assert!(matches!(cli.command, Commands::Init(_)));
1708 }
1709
1710 #[test]
1711 fn test_parse_config_command() {
1712 let cli = Cli::parse_from(["probar", "config", "--show"]);
1713 if let Commands::Config(args) = cli.command {
1714 assert!(args.show);
1715 } else {
1716 panic!("expected Config command");
1717 }
1718 }
1719
1720 #[test]
1721 fn test_global_verbose_flag() {
1722 let cli = Cli::parse_from(["probar", "-vvv", "test"]);
1723 assert_eq!(cli.verbose, 3);
1724 }
1725
1726 #[test]
1727 fn test_global_quiet_flag() {
1728 let cli = Cli::parse_from(["probar", "-q", "test"]);
1729 assert!(cli.quiet);
1730 }
1731
1732 #[test]
1733 fn test_global_color_flag() {
1734 let cli = Cli::parse_from(["probar", "--color", "never", "test"]);
1735 assert!(matches!(cli.color, ColorArg::Never));
1736 }
1737 }
1738
1739 mod format_tests {
1740 use super::*;
1741
1742 #[test]
1743 fn test_record_format_default() {
1744 let format = RecordFormat::default();
1745 assert!(matches!(format, RecordFormat::Gif));
1746 }
1747
1748 #[test]
1749 fn test_report_format_default() {
1750 let format = ReportFormat::default();
1751 assert!(matches!(format, ReportFormat::Html));
1752 }
1753
1754 #[test]
1755 fn test_color_arg_conversion() {
1756 use crate::config::ColorChoice;
1757
1758 let auto: ColorChoice = ColorArg::Auto.into();
1759 assert!(matches!(auto, ColorChoice::Auto));
1760
1761 let always: ColorChoice = ColorArg::Always.into();
1762 assert!(matches!(always, ColorChoice::Always));
1763
1764 let never: ColorChoice = ColorArg::Never.into();
1765 assert!(matches!(never, ColorChoice::Never));
1766 }
1767 }
1768
1769 mod record_format_tests {
1770 use super::*;
1771
1772 #[test]
1773 fn test_default() {
1774 let format = RecordFormat::default();
1775 assert!(matches!(format, RecordFormat::Gif));
1776 }
1777
1778 #[test]
1779 fn test_all_variants() {
1780 let _ = RecordFormat::Gif;
1781 let _ = RecordFormat::Png;
1782 let _ = RecordFormat::Svg;
1783 let _ = RecordFormat::Mp4;
1784 }
1785
1786 #[test]
1787 fn test_debug() {
1788 let debug = format!("{:?}", RecordFormat::Gif);
1789 assert!(debug.contains("Gif"));
1790 }
1791
1792 #[test]
1793 fn test_clone() {
1794 let format = RecordFormat::Mp4;
1795 let cloned = format;
1796 assert!(matches!(cloned, RecordFormat::Mp4));
1797 }
1798 }
1799
1800 mod report_format_tests {
1801 use super::*;
1802
1803 #[test]
1804 fn test_default() {
1805 let format = ReportFormat::default();
1806 assert!(matches!(format, ReportFormat::Html));
1807 }
1808
1809 #[test]
1810 fn test_all_variants() {
1811 let _ = ReportFormat::Html;
1812 let _ = ReportFormat::Junit;
1813 let _ = ReportFormat::Lcov;
1814 let _ = ReportFormat::Cobertura;
1815 let _ = ReportFormat::Json;
1816 }
1817
1818 #[test]
1819 fn test_debug() {
1820 let debug = format!("{:?}", ReportFormat::Junit);
1821 assert!(debug.contains("Junit"));
1822 }
1823 }
1824
1825 mod test_args_tests {
1826 use super::*;
1827
1828 #[test]
1829 fn test_defaults() {
1830 let args = TestArgs {
1832 filter: None,
1833 parallel: 0,
1834 coverage: false,
1835 mutants: false,
1836 fail_fast: false,
1837 watch: false,
1838 timeout: 30000,
1839 output: PathBuf::from("target/probar"),
1840 skip_compile: false,
1841 };
1842 assert!(!args.coverage);
1843 assert_eq!(args.timeout, 30000);
1844 }
1845
1846 #[test]
1847 fn test_debug() {
1848 let args = TestArgs {
1849 filter: Some("test_*".to_string()),
1850 parallel: 4,
1851 coverage: true,
1852 mutants: false,
1853 fail_fast: true,
1854 watch: false,
1855 timeout: 5000,
1856 output: PathBuf::from("target"),
1857 skip_compile: false,
1858 };
1859 let debug = format!("{args:?}");
1860 assert!(debug.contains("TestArgs"));
1861 }
1862
1863 #[test]
1864 fn test_skip_compile_flag() {
1865 let args = TestArgs {
1866 filter: None,
1867 parallel: 0,
1868 coverage: false,
1869 mutants: false,
1870 fail_fast: false,
1871 watch: false,
1872 timeout: 30000,
1873 output: PathBuf::from("target/probar"),
1874 skip_compile: true,
1875 };
1876 assert!(args.skip_compile);
1877 }
1878 }
1879
1880 mod record_args_tests {
1881 use super::*;
1882
1883 #[test]
1884 fn test_creation() {
1885 let args = RecordArgs {
1886 test: "my_test".to_string(),
1887 format: RecordFormat::Gif,
1888 output: None,
1889 fps: 10,
1890 quality: 80,
1891 };
1892 assert_eq!(args.test, "my_test");
1893 assert_eq!(args.fps, 10);
1894 }
1895
1896 #[test]
1897 fn test_debug() {
1898 let args = RecordArgs {
1899 test: "test".to_string(),
1900 format: RecordFormat::Png,
1901 output: Some(PathBuf::from("out.png")),
1902 fps: 30,
1903 quality: 100,
1904 };
1905 let debug = format!("{args:?}");
1906 assert!(debug.contains("RecordArgs"));
1907 }
1908 }
1909
1910 mod report_args_tests {
1911 use super::*;
1912
1913 #[test]
1914 fn test_creation() {
1915 let args = ReportArgs {
1916 format: ReportFormat::Lcov,
1917 output: PathBuf::from("coverage"),
1918 open: true,
1919 };
1920 assert!(args.open);
1921 }
1922
1923 #[test]
1924 fn test_debug() {
1925 let args = ReportArgs {
1926 format: ReportFormat::Html,
1927 output: PathBuf::from("reports"),
1928 open: false,
1929 };
1930 let debug = format!("{args:?}");
1931 assert!(debug.contains("ReportArgs"));
1932 }
1933 }
1934
1935 mod init_args_tests {
1936 use super::*;
1937
1938 #[test]
1939 fn test_creation() {
1940 let args = InitArgs {
1941 path: PathBuf::from("."),
1942 force: false,
1943 };
1944 assert!(!args.force);
1945 }
1946 }
1947
1948 mod config_args_tests {
1949 use super::*;
1950
1951 #[test]
1952 fn test_creation() {
1953 let args = ConfigArgs {
1954 show: false,
1955 set: None,
1956 reset: false,
1957 };
1958 assert!(!args.show);
1959 }
1960 }
1961
1962 mod cli_additional_tests {
1963 use super::*;
1964
1965 #[test]
1966 fn test_cli_debug() {
1967 let cli = Cli {
1968 verbose: 0,
1969 quiet: false,
1970 color: ColorArg::Auto,
1971 command: Commands::Config(ConfigArgs {
1972 show: true,
1973 set: None,
1974 reset: false,
1975 }),
1976 };
1977 let debug = format!("{cli:?}");
1978 assert!(debug.contains("Cli"));
1979 }
1980 }
1981
1982 mod coverage_tests {
1983 use super::*;
1984
1985 #[test]
1986 fn test_parse_coverage_command() {
1987 let cli = Cli::parse_from(["probar", "coverage"]);
1988 assert!(matches!(cli.command, Commands::Coverage(_)));
1989 }
1990
1991 #[test]
1992 fn test_parse_coverage_with_png() {
1993 let cli = Cli::parse_from(["probar", "coverage", "--png", "output.png"]);
1994 if let Commands::Coverage(args) = cli.command {
1995 assert_eq!(args.png, Some(PathBuf::from("output.png")));
1996 } else {
1997 panic!("expected Coverage command");
1998 }
1999 }
2000
2001 #[test]
2002 fn test_parse_coverage_with_palette() {
2003 let cli = Cli::parse_from(["probar", "coverage", "--palette", "magma"]);
2004 if let Commands::Coverage(args) = cli.command {
2005 assert!(matches!(args.palette, PaletteArg::Magma));
2006 } else {
2007 panic!("expected Coverage command");
2008 }
2009 }
2010
2011 #[test]
2012 fn test_parse_coverage_with_legend() {
2013 let cli = Cli::parse_from(["probar", "coverage", "--legend"]);
2014 if let Commands::Coverage(args) = cli.command {
2015 assert!(args.legend);
2016 } else {
2017 panic!("expected Coverage command");
2018 }
2019 }
2020
2021 #[test]
2022 fn test_parse_coverage_with_gaps() {
2023 let cli = Cli::parse_from(["probar", "coverage", "--gaps"]);
2024 if let Commands::Coverage(args) = cli.command {
2025 assert!(args.gaps);
2026 } else {
2027 panic!("expected Coverage command");
2028 }
2029 }
2030
2031 #[test]
2032 fn test_parse_coverage_with_title() {
2033 let cli = Cli::parse_from(["probar", "coverage", "--title", "My Coverage"]);
2034 if let Commands::Coverage(args) = cli.command {
2035 assert_eq!(args.title, Some("My Coverage".to_string()));
2036 } else {
2037 panic!("expected Coverage command");
2038 }
2039 }
2040
2041 #[test]
2042 fn test_parse_coverage_with_dimensions() {
2043 let cli = Cli::parse_from(["probar", "coverage", "--width", "1024", "--height", "768"]);
2044 if let Commands::Coverage(args) = cli.command {
2045 assert_eq!(args.width, 1024);
2046 assert_eq!(args.height, 768);
2047 } else {
2048 panic!("expected Coverage command");
2049 }
2050 }
2051
2052 #[test]
2053 fn test_parse_coverage_full_options() {
2054 let cli = Cli::parse_from([
2055 "probar",
2056 "coverage",
2057 "--png",
2058 "heatmap.png",
2059 "--palette",
2060 "heat",
2061 "--legend",
2062 "--gaps",
2063 "--title",
2064 "Test Coverage",
2065 "--width",
2066 "1920",
2067 "--height",
2068 "1080",
2069 ]);
2070 if let Commands::Coverage(args) = cli.command {
2071 assert_eq!(args.png, Some(PathBuf::from("heatmap.png")));
2072 assert!(matches!(args.palette, PaletteArg::Heat));
2073 assert!(args.legend);
2074 assert!(args.gaps);
2075 assert_eq!(args.title, Some("Test Coverage".to_string()));
2076 assert_eq!(args.width, 1920);
2077 assert_eq!(args.height, 1080);
2078 } else {
2079 panic!("expected Coverage command");
2080 }
2081 }
2082
2083 #[test]
2084 fn test_palette_default() {
2085 let palette = PaletteArg::default();
2086 assert!(matches!(palette, PaletteArg::Viridis));
2087 }
2088
2089 #[test]
2090 fn test_coverage_args_defaults() {
2091 let args = CoverageArgs {
2092 png: None,
2093 json: None,
2094 palette: PaletteArg::default(),
2095 legend: false,
2096 gaps: false,
2097 title: None,
2098 width: 800,
2099 height: 600,
2100 input: None,
2101 };
2102 assert_eq!(args.width, 800);
2103 assert_eq!(args.height, 600);
2104 assert!(matches!(args.palette, PaletteArg::Viridis));
2105 }
2106
2107 #[test]
2108 fn test_coverage_args_debug() {
2109 let args = CoverageArgs {
2110 png: Some(PathBuf::from("test.png")),
2111 json: None,
2112 palette: PaletteArg::Magma,
2113 legend: true,
2114 gaps: true,
2115 title: Some("Test".to_string()),
2116 width: 640,
2117 height: 480,
2118 input: None,
2119 };
2120 let debug = format!("{args:?}");
2121 assert!(debug.contains("CoverageArgs"));
2122 }
2123 }
2124
2125 mod playbook_tests {
2126 use super::*;
2127
2128 #[test]
2129 fn test_parse_playbook_command() {
2130 let cli = Cli::parse_from(["probar", "playbook", "test.yaml"]);
2131 assert!(matches!(cli.command, Commands::Playbook(_)));
2132 }
2133
2134 #[test]
2135 fn test_parse_playbook_multiple_files() {
2136 let cli = Cli::parse_from(["probar", "playbook", "a.yaml", "b.yaml", "c.yaml"]);
2137 if let Commands::Playbook(args) = cli.command {
2138 assert_eq!(args.files.len(), 3);
2139 } else {
2140 panic!("expected Playbook command");
2141 }
2142 }
2143
2144 #[test]
2145 fn test_parse_playbook_validate() {
2146 let cli = Cli::parse_from(["probar", "playbook", "test.yaml", "--validate"]);
2147 if let Commands::Playbook(args) = cli.command {
2148 assert!(args.validate);
2149 } else {
2150 panic!("expected Playbook command");
2151 }
2152 }
2153
2154 #[test]
2155 fn test_parse_playbook_export_dot() {
2156 let cli = Cli::parse_from(["probar", "playbook", "test.yaml", "--export", "dot"]);
2157 if let Commands::Playbook(args) = cli.command {
2158 assert!(matches!(args.export, Some(DiagramFormat::Dot)));
2159 } else {
2160 panic!("expected Playbook command");
2161 }
2162 }
2163
2164 #[test]
2165 fn test_parse_playbook_export_svg() {
2166 let cli = Cli::parse_from([
2167 "probar",
2168 "playbook",
2169 "test.yaml",
2170 "--export",
2171 "svg",
2172 "--export-output",
2173 "diagram.svg",
2174 ]);
2175 if let Commands::Playbook(args) = cli.command {
2176 assert!(matches!(args.export, Some(DiagramFormat::Svg)));
2177 assert_eq!(args.export_output, Some(PathBuf::from("diagram.svg")));
2178 } else {
2179 panic!("expected Playbook command");
2180 }
2181 }
2182
2183 #[test]
2184 fn test_parse_playbook_mutate() {
2185 let cli = Cli::parse_from(["probar", "playbook", "test.yaml", "--mutate"]);
2186 if let Commands::Playbook(args) = cli.command {
2187 assert!(args.mutate);
2188 } else {
2189 panic!("expected Playbook command");
2190 }
2191 }
2192
2193 #[test]
2194 fn test_parse_playbook_mutation_classes() {
2195 let cli = Cli::parse_from([
2196 "probar",
2197 "playbook",
2198 "test.yaml",
2199 "--mutate",
2200 "--mutation-classes",
2201 "M1,M2,M3",
2202 ]);
2203 if let Commands::Playbook(args) = cli.command {
2204 assert!(args.mutate);
2205 let classes = args.mutation_classes.expect("mutation classes");
2206 assert_eq!(classes.len(), 3);
2207 assert!(classes.contains(&"M1".to_string()));
2208 assert!(classes.contains(&"M2".to_string()));
2209 assert!(classes.contains(&"M3".to_string()));
2210 } else {
2211 panic!("expected Playbook command");
2212 }
2213 }
2214
2215 #[test]
2216 fn test_parse_playbook_fail_fast() {
2217 let cli = Cli::parse_from(["probar", "playbook", "test.yaml", "--fail-fast"]);
2218 if let Commands::Playbook(args) = cli.command {
2219 assert!(args.fail_fast);
2220 } else {
2221 panic!("expected Playbook command");
2222 }
2223 }
2224
2225 #[test]
2226 fn test_parse_playbook_continue_on_error() {
2227 let cli = Cli::parse_from(["probar", "playbook", "test.yaml", "--continue-on-error"]);
2228 if let Commands::Playbook(args) = cli.command {
2229 assert!(args.continue_on_error);
2230 } else {
2231 panic!("expected Playbook command");
2232 }
2233 }
2234
2235 #[test]
2236 fn test_parse_playbook_format_json() {
2237 let cli = Cli::parse_from(["probar", "playbook", "test.yaml", "--format", "json"]);
2238 if let Commands::Playbook(args) = cli.command {
2239 assert!(matches!(args.format, PlaybookOutputFormat::Json));
2240 } else {
2241 panic!("expected Playbook command");
2242 }
2243 }
2244
2245 #[test]
2246 fn test_parse_playbook_output_dir() {
2247 let cli =
2248 Cli::parse_from(["probar", "playbook", "test.yaml", "--output", "results/pb"]);
2249 if let Commands::Playbook(args) = cli.command {
2250 assert_eq!(args.output, PathBuf::from("results/pb"));
2251 } else {
2252 panic!("expected Playbook command");
2253 }
2254 }
2255
2256 #[test]
2257 fn test_playbook_args_defaults() {
2258 let args = PlaybookArgs {
2259 files: vec![PathBuf::from("test.yaml")],
2260 validate: false,
2261 export: None,
2262 export_output: None,
2263 mutate: false,
2264 mutation_classes: None,
2265 fail_fast: false,
2266 continue_on_error: false,
2267 format: PlaybookOutputFormat::default(),
2268 output: PathBuf::from("target/probar/playbooks"),
2269 };
2270 assert!(!args.validate);
2271 assert!(!args.mutate);
2272 assert!(matches!(args.format, PlaybookOutputFormat::Text));
2273 }
2274
2275 #[test]
2276 fn test_playbook_args_debug() {
2277 let args = PlaybookArgs {
2278 files: vec![PathBuf::from("login.yaml")],
2279 validate: true,
2280 export: Some(DiagramFormat::Svg),
2281 export_output: Some(PathBuf::from("out.svg")),
2282 mutate: true,
2283 mutation_classes: Some(vec!["M1".to_string(), "M2".to_string()]),
2284 fail_fast: true,
2285 continue_on_error: false,
2286 format: PlaybookOutputFormat::Json,
2287 output: PathBuf::from("output"),
2288 };
2289 let debug = format!("{args:?}");
2290 assert!(debug.contains("PlaybookArgs"));
2291 }
2292
2293 #[test]
2294 fn test_diagram_format_debug() {
2295 let dot_debug = format!("{:?}", DiagramFormat::Dot);
2296 assert!(dot_debug.contains("Dot"));
2297
2298 let svg_debug = format!("{:?}", DiagramFormat::Svg);
2299 assert!(svg_debug.contains("Svg"));
2300 }
2301
2302 #[test]
2303 fn test_playbook_output_format_default() {
2304 let format = PlaybookOutputFormat::default();
2305 assert!(matches!(format, PlaybookOutputFormat::Text));
2306 }
2307
2308 #[test]
2309 fn test_playbook_output_format_all_variants() {
2310 let _ = PlaybookOutputFormat::Text;
2311 let _ = PlaybookOutputFormat::Json;
2312 let _ = PlaybookOutputFormat::Junit;
2313 }
2314 }
2315
2316 mod av_sync_tests {
2317 use super::*;
2318
2319 #[test]
2320 fn test_parse_av_sync_check() {
2321 let cli = Cli::parse_from(["probar", "av-sync", "check", "video.mp4"]);
2322 assert!(matches!(cli.command, Commands::AvSync(_)));
2323 }
2324
2325 #[test]
2326 fn test_parse_av_sync_check_with_edl() {
2327 let cli = Cli::parse_from([
2328 "probar",
2329 "av-sync",
2330 "check",
2331 "video.mp4",
2332 "--edl",
2333 "video.edl.json",
2334 ]);
2335 if let Commands::AvSync(args) = cli.command {
2336 if let AvSyncSubcommand::Check(check_args) = args.subcommand {
2337 assert_eq!(check_args.edl, Some(PathBuf::from("video.edl.json")));
2338 } else {
2339 panic!("expected Check subcommand");
2340 }
2341 } else {
2342 panic!("expected AvSync command");
2343 }
2344 }
2345
2346 #[test]
2347 fn test_parse_av_sync_check_with_tolerance() {
2348 let cli = Cli::parse_from([
2349 "probar",
2350 "av-sync",
2351 "check",
2352 "video.mp4",
2353 "--tolerance-ms",
2354 "30",
2355 ]);
2356 if let Commands::AvSync(args) = cli.command {
2357 if let AvSyncSubcommand::Check(check_args) = args.subcommand {
2358 assert!((check_args.tolerance_ms - 30.0).abs() < f64::EPSILON);
2359 } else {
2360 panic!("expected Check subcommand");
2361 }
2362 } else {
2363 panic!("expected AvSync command");
2364 }
2365 }
2366
2367 #[test]
2368 fn test_parse_av_sync_report() {
2369 let cli = Cli::parse_from(["probar", "av-sync", "report", "/output/dir"]);
2370 if let Commands::AvSync(args) = cli.command {
2371 if let AvSyncSubcommand::Report(report_args) = args.subcommand {
2372 assert_eq!(report_args.dir, PathBuf::from("/output/dir"));
2373 } else {
2374 panic!("expected Report subcommand");
2375 }
2376 } else {
2377 panic!("expected AvSync command");
2378 }
2379 }
2380
2381 #[test]
2382 fn test_parse_av_sync_check_detailed() {
2383 let cli = Cli::parse_from(["probar", "av-sync", "check", "video.mp4", "--detailed"]);
2384 if let Commands::AvSync(args) = cli.command {
2385 if let AvSyncSubcommand::Check(check_args) = args.subcommand {
2386 assert!(check_args.detailed);
2387 } else {
2388 panic!("expected Check subcommand");
2389 }
2390 } else {
2391 panic!("expected AvSync command");
2392 }
2393 }
2394
2395 #[test]
2396 fn test_parse_av_sync_report_with_output() {
2397 let cli = Cli::parse_from([
2398 "probar",
2399 "av-sync",
2400 "report",
2401 "/output",
2402 "-o",
2403 "report.json",
2404 ]);
2405 if let Commands::AvSync(args) = cli.command {
2406 if let AvSyncSubcommand::Report(report_args) = args.subcommand {
2407 assert_eq!(report_args.output, Some(PathBuf::from("report.json")));
2408 } else {
2409 panic!("expected Report subcommand");
2410 }
2411 } else {
2412 panic!("expected AvSync command");
2413 }
2414 }
2415
2416 #[test]
2417 fn test_av_sync_output_format_default() {
2418 let format = AvSyncOutputFormat::default();
2419 assert!(matches!(format, AvSyncOutputFormat::Text));
2420 }
2421
2422 #[test]
2423 fn test_parse_av_sync_check_json_format() {
2424 let cli = Cli::parse_from([
2425 "probar",
2426 "av-sync",
2427 "check",
2428 "video.mp4",
2429 "--format",
2430 "json",
2431 ]);
2432 if let Commands::AvSync(args) = cli.command {
2433 if let AvSyncSubcommand::Check(check_args) = args.subcommand {
2434 assert!(matches!(check_args.format, AvSyncOutputFormat::Json));
2435 } else {
2436 panic!("expected Check subcommand");
2437 }
2438 } else {
2439 panic!("expected AvSync command");
2440 }
2441 }
2442 }
2443
2444 mod audio_tests {
2445 use super::*;
2446
2447 #[test]
2448 fn test_parse_audio_check() {
2449 let cli = Cli::parse_from(["probar", "audio", "check", "video.mp4"]);
2450 assert!(matches!(cli.command, Commands::Audio(_)));
2451 }
2452
2453 #[test]
2454 fn test_parse_audio_check_with_options() {
2455 let cli = Cli::parse_from([
2456 "probar",
2457 "audio",
2458 "check",
2459 "video.mp4",
2460 "--min-rms-dbfs",
2461 "-30",
2462 "--sample-rate",
2463 "44100",
2464 ]);
2465 if let Commands::Audio(args) = cli.command {
2466 if let AudioSubcommand::Check(check_args) = args.subcommand {
2467 assert!((check_args.min_rms_dbfs - (-30.0)).abs() < f64::EPSILON);
2468 assert_eq!(check_args.sample_rate, 44100);
2469 } else {
2470 panic!("expected Check subcommand");
2471 }
2472 } else {
2473 panic!("expected Audio command");
2474 }
2475 }
2476
2477 #[test]
2478 fn test_output_format_default() {
2479 let format = OutputFormat::default();
2480 assert!(matches!(format, OutputFormat::Text));
2481 }
2482 }
2483
2484 mod video_tests {
2485 use super::*;
2486
2487 #[test]
2488 fn test_parse_video_check() {
2489 let cli = Cli::parse_from(["probar", "video", "check", "video.mp4"]);
2490 assert!(matches!(cli.command, Commands::Video(_)));
2491 }
2492
2493 #[test]
2494 fn test_parse_video_check_with_expectations() {
2495 let cli = Cli::parse_from([
2496 "probar",
2497 "video",
2498 "check",
2499 "video.mp4",
2500 "--width",
2501 "1920",
2502 "--height",
2503 "1080",
2504 "--fps",
2505 "24",
2506 "--codec",
2507 "h264",
2508 "--require-audio",
2509 ]);
2510 if let Commands::Video(args) = cli.command {
2511 if let VideoSubcommand::Check(check_args) = args.subcommand {
2512 assert_eq!(check_args.width, Some(1920));
2513 assert_eq!(check_args.height, Some(1080));
2514 assert!((check_args.fps.unwrap() - 24.0).abs() < f64::EPSILON);
2515 assert_eq!(check_args.codec.as_deref(), Some("h264"));
2516 assert!(check_args.require_audio);
2517 } else {
2518 panic!("expected Check subcommand");
2519 }
2520 } else {
2521 panic!("expected Video command");
2522 }
2523 }
2524 }
2525
2526 mod animation_tests {
2527 use super::*;
2528
2529 #[test]
2530 fn test_parse_animation_check() {
2531 let cli = Cli::parse_from([
2532 "probar",
2533 "animation",
2534 "check",
2535 "timeline.json",
2536 "observed.json",
2537 ]);
2538 assert!(matches!(cli.command, Commands::Animation(_)));
2539 }
2540
2541 #[test]
2542 fn test_parse_animation_check_with_tolerance() {
2543 let cli = Cli::parse_from([
2544 "probar",
2545 "animation",
2546 "check",
2547 "timeline.json",
2548 "observed.json",
2549 "--tolerance-ms",
2550 "30",
2551 ]);
2552 if let Commands::Animation(args) = cli.command {
2553 if let AnimationSubcommand::Check(check_args) = args.subcommand {
2554 assert!((check_args.tolerance_ms - 30.0).abs() < f64::EPSILON);
2555 } else {
2556 panic!("expected Check subcommand");
2557 }
2558 } else {
2559 panic!("expected Animation command");
2560 }
2561 }
2562 }
2563
2564 mod stress_args_tests {
2565 use super::*;
2566
2567 fn make_stress_args(
2568 atomics: bool,
2569 worker_msg: bool,
2570 render: bool,
2571 trace: bool,
2572 full: bool,
2573 mode: &str,
2574 ) -> StressArgs {
2575 StressArgs {
2576 mode: mode.to_string(),
2577 duration: 30,
2578 concurrency: 4,
2579 output: "text".to_string(),
2580 atomics,
2581 worker_msg,
2582 render,
2583 trace,
2584 full,
2585 }
2586 }
2587
2588 #[test]
2589 fn test_get_mode_atomics() {
2590 let args = make_stress_args(true, false, false, false, false, "default");
2591 assert_eq!(args.get_mode(), "atomics");
2592 }
2593
2594 #[test]
2595 fn test_get_mode_worker_msg() {
2596 let args = make_stress_args(false, true, false, false, false, "default");
2597 assert_eq!(args.get_mode(), "worker-msg");
2598 }
2599
2600 #[test]
2601 fn test_get_mode_render() {
2602 let args = make_stress_args(false, false, true, false, false, "default");
2603 assert_eq!(args.get_mode(), "render");
2604 }
2605
2606 #[test]
2607 fn test_get_mode_trace() {
2608 let args = make_stress_args(false, false, false, true, false, "default");
2609 assert_eq!(args.get_mode(), "trace");
2610 }
2611
2612 #[test]
2613 fn test_get_mode_full() {
2614 let args = make_stress_args(false, false, false, false, true, "default");
2615 assert_eq!(args.get_mode(), "full");
2616 }
2617
2618 #[test]
2619 fn test_get_mode_default() {
2620 let args = make_stress_args(false, false, false, false, false, "custom-mode");
2621 assert_eq!(args.get_mode(), "custom-mode");
2622 }
2623
2624 #[test]
2625 fn test_stress_args_debug() {
2626 let args = make_stress_args(true, false, false, false, false, "atomics");
2627 let debug = format!("{args:?}");
2628 assert!(debug.contains("StressArgs"));
2629 }
2630
2631 #[test]
2632 fn test_parse_stress_command() {
2633 let cli = Cli::parse_from(["probar", "stress"]);
2634 assert!(matches!(cli.command, Commands::Stress(_)));
2635 }
2636
2637 #[test]
2638 fn test_parse_stress_with_duration() {
2639 let cli = Cli::parse_from(["probar", "stress", "--duration", "60"]);
2640 if let Commands::Stress(args) = cli.command {
2641 assert_eq!(args.duration, 60);
2642 } else {
2643 panic!("expected Stress command");
2644 }
2645 }
2646
2647 #[test]
2648 fn test_parse_stress_with_concurrency() {
2649 let cli = Cli::parse_from(["probar", "stress", "--concurrency", "8"]);
2650 if let Commands::Stress(args) = cli.command {
2651 assert_eq!(args.concurrency, 8);
2652 } else {
2653 panic!("expected Stress command");
2654 }
2655 }
2656
2657 #[test]
2658 fn test_parse_stress_with_atomics_flag() {
2659 let cli = Cli::parse_from(["probar", "stress", "--atomics"]);
2660 if let Commands::Stress(args) = cli.command {
2661 assert!(args.atomics);
2662 assert_eq!(args.get_mode(), "atomics");
2663 } else {
2664 panic!("expected Stress command");
2665 }
2666 }
2667 }
2668}