1use clap::{Parser, Subcommand};
7use std::path::{Path, PathBuf};
8
9mod commands;
10pub mod error;
11mod output;
12
13pub use error::CliError;
14
15pub mod qa_types {
17 pub use crate::commands::qa::{GateResult, QaReport, SystemInfo};
18}
19
20#[cfg(feature = "inference")]
21pub mod federation;
22
23use commands::{
25 bench, canary, canary::CanaryCommands, cbtop, chat, compare_hf, convert, debug, diff, eval,
26 explain, export, flow, hex, import, inspect, lint, merge, oracle, probar, profile, ptx_explain,
27 publish, pull, qa, rosetta, rosetta::RosettaCommands, run, serve, showcase, tensors, trace,
28 tree, tui, tune, validate,
29};
30
31#[derive(Parser, Debug)]
36#[command(name = "apr")]
37#[command(author, version = concat!(env!("CARGO_PKG_VERSION"), " (", env!("APR_GIT_SHA"), ")"), about, long_about = None)]
38#[command(propagate_version = true)]
39pub struct Cli {
40 #[command(subcommand)]
41 pub command: Box<Commands>,
42
43 #[arg(long, global = true)]
45 pub json: bool,
46
47 #[arg(short, long, global = true)]
49 pub verbose: bool,
50
51 #[arg(short, long, global = true)]
53 pub quiet: bool,
54
55 #[arg(long, global = true)]
57 pub offline: bool,
58
59 #[arg(long, global = true)]
61 pub skip_contract: bool,
62}
63
64#[derive(Subcommand, Debug)]
65pub enum Commands {
66 Run {
68 #[arg(value_name = "SOURCE")]
70 source: String,
71
72 #[arg(value_name = "PROMPT")]
74 positional_prompt: Option<String>,
75
76 #[arg(short, long)]
78 input: Option<PathBuf>,
79
80 #[arg(short, long)]
82 prompt: Option<String>,
83
84 #[arg(short = 'n', long, default_value = "32")]
86 max_tokens: usize,
87
88 #[arg(long)]
90 stream: bool,
91
92 #[arg(short, long)]
94 language: Option<String>,
95
96 #[arg(short, long)]
98 task: Option<String>,
99
100 #[arg(short = 'f', long, default_value = "text")]
102 format: String,
103
104 #[arg(long, conflicts_with = "gpu")]
106 no_gpu: bool,
107
108 #[arg(long, conflicts_with = "no_gpu")]
110 gpu: bool,
111
112 #[arg(long)]
114 offline: bool,
115
116 #[arg(long)]
118 benchmark: bool,
119
120 #[arg(long)]
122 trace: bool,
123
124 #[arg(long, value_delimiter = ',')]
126 trace_steps: Option<Vec<String>>,
127
128 #[arg(long)]
130 trace_verbose: bool,
131
132 #[arg(long, value_name = "FILE")]
134 trace_output: Option<PathBuf>,
135
136 #[arg(long, value_name = "LEVEL", default_value = "basic")]
138 trace_level: String,
139
140 #[arg(long)]
142 trace_payload: bool,
143
144 #[arg(long)]
146 profile: bool,
147
148 #[arg(long)]
153 chat: bool,
154
155 #[arg(short, long)]
157 verbose: bool,
158 },
159
160 Serve {
162 #[arg(value_name = "FILE")]
164 file: PathBuf,
165
166 #[arg(short, long, default_value = "8080")]
168 port: u16,
169
170 #[arg(long, default_value = "127.0.0.1")]
172 host: String,
173
174 #[arg(long)]
176 no_cors: bool,
177
178 #[arg(long)]
180 no_metrics: bool,
181
182 #[arg(long)]
184 no_gpu: bool,
185
186 #[arg(long)]
188 gpu: bool,
189
190 #[arg(long)]
192 batch: bool,
193
194 #[arg(long)]
196 trace: bool,
197
198 #[arg(long, value_name = "LEVEL", default_value = "basic")]
200 trace_level: String,
201
202 #[arg(long)]
204 profile: bool,
205 },
206
207 Inspect {
209 #[arg(value_name = "FILE")]
211 file: PathBuf,
212
213 #[arg(long)]
215 vocab: bool,
216
217 #[arg(long)]
219 filters: bool,
220
221 #[arg(long)]
223 weights: bool,
224
225 #[arg(long)]
227 json: bool,
228 },
229
230 Debug {
232 #[arg(value_name = "FILE")]
234 file: PathBuf,
235
236 #[arg(long)]
238 drama: bool,
239
240 #[arg(long)]
242 hex: bool,
243
244 #[arg(long)]
246 strings: bool,
247
248 #[arg(long, default_value = "256")]
250 limit: usize,
251 },
252
253 Validate {
255 #[arg(value_name = "FILE")]
257 file: PathBuf,
258
259 #[arg(long)]
261 quality: bool,
262
263 #[arg(long)]
265 strict: bool,
266
267 #[arg(long)]
269 min_score: Option<u8>,
270 },
271
272 Diff {
274 #[arg(value_name = "FILE1")]
276 file1: PathBuf,
277
278 #[arg(value_name = "FILE2")]
280 file2: PathBuf,
281
282 #[arg(long)]
284 weights: bool,
285
286 #[arg(long)]
288 values: bool,
289
290 #[arg(long)]
292 filter: Option<String>,
293
294 #[arg(long, default_value = "10")]
296 limit: usize,
297
298 #[arg(long)]
300 transpose_aware: bool,
301
302 #[arg(long)]
304 json: bool,
305 },
306
307 Tensors {
309 #[arg(value_name = "FILE")]
311 file: PathBuf,
312
313 #[arg(long)]
315 stats: bool,
316
317 #[arg(long)]
319 filter: Option<String>,
320
321 #[arg(long, default_value = "0")]
323 limit: usize,
324
325 #[arg(long)]
327 json: bool,
328 },
329
330 Trace {
332 #[arg(value_name = "FILE")]
334 file: PathBuf,
335
336 #[arg(long)]
338 layer: Option<String>,
339
340 #[arg(long)]
342 reference: Option<PathBuf>,
343
344 #[arg(long)]
346 json: bool,
347
348 #[arg(short, long)]
350 verbose: bool,
351
352 #[arg(long)]
354 payload: bool,
355
356 #[arg(long)]
358 diff: bool,
359
360 #[arg(long)]
362 interactive: bool,
363 },
364
365 Lint {
367 #[arg(value_name = "FILE")]
369 file: PathBuf,
370 },
371
372 Explain {
374 #[arg(value_name = "CODE")]
376 code: Option<String>,
377
378 #[arg(short, long)]
380 file: Option<PathBuf>,
381
382 #[arg(long)]
384 tensor: Option<String>,
385 },
386
387 Canary {
389 #[command(subcommand)]
390 command: CanaryCommands,
391 },
392
393 Export {
395 #[arg(value_name = "FILE")]
397 file: PathBuf,
398
399 #[arg(long, default_value = "safetensors")]
401 format: String,
402
403 #[arg(short, long)]
405 output: PathBuf,
406
407 #[arg(long)]
409 quantize: Option<String>,
410 },
411
412 Import {
414 #[arg(value_name = "SOURCE")]
416 source: String,
417
418 #[arg(short, long)]
420 output: Option<PathBuf>,
421
422 #[arg(long, default_value = "auto")]
424 arch: String,
425
426 #[arg(long)]
428 quantize: Option<String>,
429
430 #[arg(long)]
432 strict: bool,
433
434 #[arg(long)]
437 preserve_q4k: bool,
438
439 #[arg(long)]
442 tokenizer: Option<PathBuf>,
443
444 #[arg(long)]
447 enforce_provenance: bool,
448 },
449
450 Pull {
452 #[arg(value_name = "MODEL")]
454 model_ref: String,
455
456 #[arg(long)]
458 force: bool,
459 },
460
461 #[command(name = "list", alias = "ls")]
463 List,
464
465 #[command(name = "rm", alias = "remove")]
467 Rm {
468 #[arg(value_name = "MODEL")]
470 model_ref: String,
471 },
472
473 Convert {
475 #[arg(value_name = "FILE")]
477 file: PathBuf,
478
479 #[arg(long)]
481 quantize: Option<String>,
482
483 #[arg(long)]
485 compress: Option<String>,
486
487 #[arg(short, long)]
489 output: PathBuf,
490
491 #[arg(short, long)]
493 force: bool,
494 },
495
496 Merge {
498 #[arg(value_name = "FILES", num_args = 2..)]
500 files: Vec<PathBuf>,
501
502 #[arg(long, default_value = "average")]
504 strategy: String,
505
506 #[arg(short, long)]
508 output: PathBuf,
509
510 #[arg(long, value_delimiter = ',')]
512 weights: Option<Vec<f32>>,
513 },
514
515 Tui {
517 #[arg(value_name = "FILE")]
519 file: Option<PathBuf>,
520 },
521
522 Cbtop {
524 #[arg(long)]
526 model: Option<String>,
527
528 #[arg(long)]
530 attach: Option<String>,
531
532 #[arg(long, value_name = "MODEL")]
534 model_path: Option<PathBuf>,
535
536 #[arg(long)]
538 headless: bool,
539
540 #[arg(long)]
542 json: bool,
543
544 #[arg(long, value_name = "FILE")]
546 output: Option<PathBuf>,
547
548 #[arg(long)]
550 ci: bool,
551
552 #[arg(long, value_name = "TOK_S")]
554 throughput: Option<f64>,
555
556 #[arg(long, value_name = "SCORE")]
558 brick_score: Option<u32>,
559
560 #[arg(long, default_value = "10")]
562 warmup: usize,
563
564 #[arg(long, default_value = "100")]
566 iterations: usize,
567
568 #[arg(long)]
570 speculative: bool,
571
572 #[arg(long, default_value = "4")]
574 speculation_k: usize,
575
576 #[arg(long, value_name = "DRAFT_MODEL")]
578 draft_model: Option<PathBuf>,
579
580 #[arg(long, default_value = "1")]
582 concurrent: usize,
583
584 #[arg(long)]
586 simulated: bool,
587 },
588
589 Probar {
591 #[arg(value_name = "FILE")]
593 file: PathBuf,
594
595 #[arg(short, long, default_value = "./probar-export")]
597 output: PathBuf,
598
599 #[arg(long, default_value = "both")]
601 format: String,
602
603 #[arg(long)]
605 golden: Option<PathBuf>,
606
607 #[arg(long)]
609 layer: Option<String>,
610 },
611
612 #[command(name = "compare-hf")]
614 CompareHf {
615 #[arg(value_name = "FILE")]
617 file: PathBuf,
618
619 #[arg(long)]
621 hf: String,
622
623 #[arg(long)]
625 tensor: Option<String>,
626
627 #[arg(long, default_value = "1e-5")]
629 threshold: f64,
630
631 #[arg(long)]
633 json: bool,
634 },
635
636 Hex {
638 #[arg(value_name = "FILE")]
640 file: PathBuf,
641
642 #[arg(long)]
644 tensor: Option<String>,
645
646 #[arg(long, default_value = "64")]
648 limit: usize,
649
650 #[arg(long)]
652 stats: bool,
653
654 #[arg(long)]
656 list: bool,
657
658 #[arg(long)]
660 json: bool,
661
662 #[arg(long)]
664 header: bool,
665
666 #[arg(long)]
668 blocks: bool,
669
670 #[arg(long)]
672 distribution: bool,
673
674 #[arg(long)]
676 contract: bool,
677
678 #[arg(long)]
680 entropy: bool,
681
682 #[arg(long)]
684 raw: bool,
685
686 #[arg(long, default_value = "0")]
688 offset: String,
689
690 #[arg(long, default_value = "16")]
692 width: usize,
693 },
694
695 Tree {
697 #[arg(value_name = "FILE")]
699 file: PathBuf,
700
701 #[arg(long)]
703 filter: Option<String>,
704
705 #[arg(long, default_value = "ascii")]
707 format: String,
708
709 #[arg(long)]
711 sizes: bool,
712
713 #[arg(long)]
715 depth: Option<usize>,
716 },
717
718 Flow {
720 #[arg(value_name = "FILE")]
722 file: PathBuf,
723
724 #[arg(long)]
726 layer: Option<String>,
727
728 #[arg(long, default_value = "full")]
730 component: String,
731
732 #[arg(short, long)]
734 verbose: bool,
735 },
736
737 Chat {
739 #[arg(value_name = "FILE")]
741 file: PathBuf,
742
743 #[arg(long, default_value = "0.7")]
745 temperature: f32,
746
747 #[arg(long, default_value = "0.9")]
749 top_p: f32,
750
751 #[arg(long, default_value = "512")]
753 max_tokens: usize,
754
755 #[arg(long)]
757 system: Option<String>,
758
759 #[arg(long)]
761 inspect: bool,
762
763 #[arg(long)]
765 no_gpu: bool,
766
767 #[arg(long)]
769 gpu: bool,
770
771 #[arg(long)]
773 trace: bool,
774
775 #[arg(long, value_delimiter = ',')]
777 trace_steps: Option<Vec<String>>,
778
779 #[arg(long)]
781 trace_verbose: bool,
782
783 #[arg(long, value_name = "FILE")]
785 trace_output: Option<PathBuf>,
786
787 #[arg(long, value_name = "LEVEL", default_value = "basic")]
789 trace_level: String,
790
791 #[arg(long)]
793 profile: bool,
794 },
795
796 Bench {
798 #[arg(value_name = "FILE")]
800 file: PathBuf,
801
802 #[arg(long, default_value = "3")]
804 warmup: usize,
805
806 #[arg(long, default_value = "5")]
808 iterations: usize,
809
810 #[arg(long, default_value = "32")]
812 max_tokens: usize,
813
814 #[arg(long)]
816 prompt: Option<String>,
817
818 #[arg(long)]
820 fast: bool,
821
822 #[arg(long)]
824 brick: Option<String>,
825 },
826
827 Eval {
829 #[arg(value_name = "FILE")]
831 file: PathBuf,
832
833 #[arg(long, default_value = "wikitext-2")]
835 dataset: String,
836
837 #[arg(long)]
839 text: Option<String>,
840
841 #[arg(long, default_value = "512")]
843 max_tokens: usize,
844
845 #[arg(long, default_value = "20.0")]
847 threshold: f32,
848 },
849
850 Profile {
852 #[arg(value_name = "FILE")]
854 file: PathBuf,
855
856 #[arg(long)]
858 granular: bool,
859
860 #[arg(long, default_value = "human")]
862 format: String,
863
864 #[arg(long)]
866 focus: Option<String>,
867
868 #[arg(long)]
870 detect_naive: bool,
871
872 #[arg(long, default_value = "10.0")]
874 threshold: f64,
875
876 #[arg(long)]
878 compare_hf: Option<String>,
879
880 #[arg(long)]
882 energy: bool,
883
884 #[arg(long)]
886 perf_grade: bool,
887
888 #[arg(long)]
890 callgraph: bool,
891
892 #[arg(long)]
894 fail_on_naive: bool,
895
896 #[arg(long, short = 'o')]
898 output: Option<PathBuf>,
899
900 #[arg(long)]
903 ci: bool,
904
905 #[arg(long)]
907 assert_throughput: Option<f64>,
908
909 #[arg(long)]
911 assert_p99: Option<f64>,
912
913 #[arg(long)]
915 assert_p50: Option<f64>,
916
917 #[arg(long, default_value = "3")]
919 warmup: usize,
920
921 #[arg(long, default_value = "10")]
923 measure: usize,
924
925 #[arg(long, default_value = "32")]
927 tokens: usize,
928
929 #[arg(long)]
931 ollama: bool,
932
933 #[arg(long)]
935 no_gpu: bool,
936
937 #[arg(long, value_name = "FILE")]
939 compare: Option<PathBuf>,
940 },
941
942 Qa {
944 #[arg(value_name = "FILE")]
946 file: PathBuf,
947
948 #[arg(long, value_name = "TPS")]
950 assert_tps: Option<f64>,
951
952 #[arg(long, value_name = "SPEEDUP")]
954 assert_speedup: Option<f64>,
955
956 #[arg(long, value_name = "SPEEDUP")]
958 assert_gpu_speedup: Option<f64>,
959
960 #[arg(long)]
962 skip_golden: bool,
963
964 #[arg(long)]
966 skip_throughput: bool,
967
968 #[arg(long)]
970 skip_ollama: bool,
971
972 #[arg(long)]
974 skip_gpu_speedup: bool,
975
976 #[arg(long)]
978 skip_contract: bool,
979
980 #[arg(long)]
982 skip_format_parity: bool,
983
984 #[arg(long)]
986 skip_ptx_parity: bool,
987
988 #[arg(long, value_name = "PATH")]
990 safetensors_path: Option<PathBuf>,
991
992 #[arg(long, default_value = "10")]
994 iterations: usize,
995
996 #[arg(long, default_value = "3")]
998 warmup: usize,
999
1000 #[arg(long, default_value = "32")]
1002 max_tokens: usize,
1003
1004 #[arg(long)]
1006 json: bool,
1007
1008 #[arg(short, long)]
1010 verbose: bool,
1011
1012 #[arg(long, value_name = "N")]
1014 min_executed: Option<usize>,
1015
1016 #[arg(long, value_name = "FILE")]
1018 previous_report: Option<PathBuf>,
1019
1020 #[arg(long, value_name = "RATIO")]
1022 regression_threshold: Option<f64>,
1023
1024 #[arg(long)]
1026 skip_gpu_state: bool,
1027
1028 #[arg(long)]
1030 skip_metadata: bool,
1031 },
1032
1033 Parity {
1035 #[arg(value_name = "FILE")]
1037 file: PathBuf,
1038
1039 #[arg(short, long, default_value = "What is 2+2?")]
1041 prompt: String,
1042
1043 #[arg(long)]
1045 assert: bool,
1046 },
1047
1048 #[command(name = "ptx-map")]
1050 PtxMap {
1051 #[arg(value_name = "FILE")]
1053 file: PathBuf,
1054
1055 #[arg(long)]
1057 kernel: Option<String>,
1058
1059 #[arg(long)]
1061 reverse: Option<String>,
1062
1063 #[arg(long)]
1065 json: bool,
1066
1067 #[arg(short, long)]
1069 verbose: bool,
1070
1071 #[arg(long)]
1073 prefill: bool,
1074 },
1075
1076 #[command(name = "ptx")]
1078 Ptx {
1079 #[arg(value_name = "FILE")]
1081 file: Option<PathBuf>,
1082
1083 #[arg(long, short)]
1085 kernel: Option<String>,
1086
1087 #[arg(long)]
1089 strict: bool,
1090
1091 #[arg(long)]
1093 bugs: bool,
1094
1095 #[arg(long)]
1097 json: bool,
1098
1099 #[arg(short, long)]
1101 verbose: bool,
1102 },
1103
1104 Tune {
1106 #[arg(value_name = "FILE")]
1108 file: Option<PathBuf>,
1109
1110 #[arg(long, short = 'm', default_value = "auto")]
1112 method: String,
1113
1114 #[arg(long, short = 'r')]
1116 rank: Option<u32>,
1117
1118 #[arg(long, default_value = "16.0")]
1120 vram: f64,
1121
1122 #[arg(long)]
1124 plan: bool,
1125
1126 #[arg(long, value_name = "SIZE")]
1128 model: Option<String>,
1129
1130 #[arg(long)]
1132 freeze_base: bool,
1133
1134 #[arg(long, value_name = "FILE")]
1136 train_data: Option<PathBuf>,
1137
1138 #[arg(long)]
1140 json: bool,
1141 },
1142
1143 Showcase {
1145 #[arg(long)]
1147 auto_verify: bool,
1148
1149 #[arg(long)]
1151 step: Option<String>,
1152
1153 #[arg(long, default_value = "small")]
1155 tier: String,
1156
1157 #[arg(long, default_value = "./models")]
1159 model_dir: PathBuf,
1160
1161 #[arg(long, default_value = "llama-cpp,ollama")]
1163 baseline: String,
1164
1165 #[arg(long)]
1167 zram: bool,
1168
1169 #[arg(long, default_value = "30")]
1171 runs: usize,
1172
1173 #[arg(long)]
1175 gpu: bool,
1176
1177 #[arg(long)]
1179 json: bool,
1180
1181 #[arg(short, long)]
1183 verbose: bool,
1184
1185 #[arg(short, long)]
1187 quiet: bool,
1188 },
1189
1190 Check {
1192 #[arg(value_name = "FILE")]
1194 file: PathBuf,
1195
1196 #[arg(long)]
1198 no_gpu: bool,
1199 },
1200
1201 Rosetta {
1203 #[command(subcommand)]
1204 action: RosettaCommands,
1205 },
1206
1207 Publish {
1209 #[arg(value_name = "DIRECTORY")]
1211 directory: PathBuf,
1212
1213 #[arg(value_name = "REPO_ID")]
1215 repo_id: String,
1216
1217 #[arg(long)]
1219 model_name: Option<String>,
1220
1221 #[arg(long, default_value = "mit")]
1223 license: String,
1224
1225 #[arg(long, default_value = "text-generation")]
1227 pipeline_tag: String,
1228
1229 #[arg(long)]
1231 library_name: Option<String>,
1232
1233 #[arg(long, value_delimiter = ',')]
1235 tags: Option<Vec<String>>,
1236
1237 #[arg(long)]
1239 message: Option<String>,
1240
1241 #[arg(long)]
1243 dry_run: bool,
1244 },
1245
1246 Oracle {
1253 #[arg(value_name = "SOURCE")]
1255 source: Option<String>,
1256
1257 #[arg(long)]
1259 family: Option<String>,
1260
1261 #[arg(long)]
1263 size: Option<String>,
1264
1265 #[arg(long)]
1267 compliance: bool,
1268
1269 #[arg(long)]
1271 tensors: bool,
1272
1273 #[arg(long)]
1275 stats: bool,
1276
1277 #[arg(long)]
1279 explain: bool,
1280
1281 #[arg(long)]
1283 kernels: bool,
1284
1285 #[arg(long)]
1287 validate: bool,
1288
1289 #[arg(long)]
1291 full: bool,
1292 },
1293}
1294
1295fn extract_model_paths(command: &Commands) -> Vec<PathBuf> {
1301 match command {
1302 Commands::Run { source, .. } => {
1304 let path = PathBuf::from(source);
1306 if path.exists() {
1307 vec![path]
1308 } else {
1309 vec![]
1310 }
1311 }
1312 Commands::Serve { file, .. }
1313 | Commands::Trace { file, .. }
1314 | Commands::Export { file, .. }
1315 | Commands::Convert { file, .. }
1316 | Commands::Probar { file, .. }
1317 | Commands::CompareHf { file, .. }
1318 | Commands::Chat { file, .. }
1319 | Commands::Bench { file, .. }
1320 | Commands::Eval { file, .. }
1321 | Commands::Profile { file, .. }
1322 | Commands::Check { file, .. } => vec![file.clone()],
1323
1324 Commands::Merge { files, .. } => files.clone(),
1325
1326 Commands::Cbtop { model_path, .. } => model_path.iter().cloned().collect(),
1327 Commands::Tui { file, .. } => file.iter().cloned().collect(),
1328 Commands::Import { source, .. } => {
1329 let path = PathBuf::from(source);
1330 if path.exists() {
1331 vec![path]
1332 } else {
1333 vec![]
1334 }
1335 }
1336
1337 Commands::Rosetta { action } => match action {
1339 RosettaCommands::Convert { source, .. }
1340 | RosettaCommands::Chain { source, .. }
1341 | RosettaCommands::Verify { source, .. } => vec![source.clone()],
1342 RosettaCommands::CompareInference {
1343 model_a, model_b, ..
1344 } => {
1345 vec![model_a.clone(), model_b.clone()]
1346 }
1347 _ => vec![],
1349 },
1350
1351 _ => vec![],
1355 }
1356}
1357
1358fn validate_model_contract(paths: &[PathBuf]) -> Result<(), CliError> {
1368 let rosetta = aprender::format::rosetta::RosettaStone::new();
1369 for path in paths {
1370 if !path.exists() {
1371 continue; }
1373 if path.to_string_lossy().ends_with(".safetensors.index.json") {
1374 validate_shard_index(path)?;
1375 continue;
1376 }
1377 validate_single_model(&rosetta, path)?;
1378 }
1379 Ok(())
1380}
1381
1382fn validate_shard_index(path: &Path) -> Result<(), CliError> {
1384 let Some(parent) = path.parent() else {
1385 return Ok(());
1386 };
1387 let manifest_path = parent.join(".apr-manifest.json");
1388 if manifest_path.exists() {
1389 validate_shard_manifest(&manifest_path, parent)?;
1390 }
1391 Ok(())
1392}
1393
1394fn validate_single_model(
1396 rosetta: &aprender::format::rosetta::RosettaStone,
1397 path: &Path,
1398) -> Result<(), CliError> {
1399 let report = rosetta.validate(path).map_err(|e| {
1400 CliError::ValidationFailed(format!(
1401 "Contract validation failed for {}: {e}",
1402 path.display()
1403 ))
1404 })?;
1405 if !report.is_valid {
1406 let violation_count: usize = report.tensors.iter().map(|t| t.failures.len()).sum();
1407 return Err(CliError::ValidationFailed(format!(
1408 "PMAT-237 CONTRACT VIOLATION: {} has {} violations in {} tensors. \
1409 Use 'apr qa {}' for details. Use --skip-contract to bypass.",
1410 path.display(),
1411 violation_count,
1412 report.failed_tensor_count,
1413 path.display(),
1414 )));
1415 }
1416 Ok(())
1417}
1418
1419fn validate_shard_manifest(
1424 manifest_path: &std::path::Path,
1425 cache_dir: &std::path::Path,
1426) -> Result<(), CliError> {
1427 let manifest_str = std::fs::read_to_string(manifest_path).map_err(|e| {
1428 CliError::ValidationFailed(format!(
1429 "Failed to read manifest {}: {e}",
1430 manifest_path.display()
1431 ))
1432 })?;
1433 let manifest: commands::pull::ShardManifest =
1434 serde_json::from_str(&manifest_str).map_err(|e| {
1435 CliError::ValidationFailed(format!(
1436 "Failed to parse manifest {}: {e}",
1437 manifest_path.display()
1438 ))
1439 })?;
1440
1441 for (filename, checksum) in &manifest.files {
1442 let file_path = cache_dir.join(filename);
1443 if !file_path.exists() {
1444 return Err(CliError::ValidationFailed(format!(
1445 "Shard '{}' is missing. Re-run 'apr pull --force' to re-download.",
1446 filename
1447 )));
1448 }
1449 let actual_size = std::fs::metadata(&file_path)
1450 .map(|m| m.len())
1451 .map_err(|e| {
1452 CliError::ValidationFailed(format!("Failed to stat shard '{}': {e}", filename))
1453 })?;
1454 if actual_size != checksum.size {
1455 return Err(CliError::ValidationFailed(format!(
1456 "Shard '{}' size mismatch: expected {} bytes, got {} bytes \
1457 (file may be truncated). Re-run 'apr pull --force' to re-download.",
1458 filename, checksum.size, actual_size
1459 )));
1460 }
1461 }
1462 Ok(())
1463}
1464
1465#[allow(clippy::too_many_arguments)]
1467fn dispatch_cbtop(
1468 model: Option<&str>,
1469 attach: Option<&str>,
1470 model_path: Option<&Path>,
1471 headless: bool,
1472 json: bool,
1473 output: Option<&Path>,
1474 ci: bool,
1475 throughput: Option<f64>,
1476 brick_score: Option<u32>,
1477 warmup: usize,
1478 iterations: usize,
1479 speculative: bool,
1480 speculation_k: usize,
1481 draft_model: Option<&Path>,
1482 concurrent: usize,
1483 simulated: bool,
1484) -> Result<(), CliError> {
1485 let (resolved_model, resolved_model_path) = if let Some(m) = model {
1486 let path = std::path::Path::new(m);
1487 let is_gguf = path
1488 .extension()
1489 .is_some_and(|ext| ext.eq_ignore_ascii_case("gguf"));
1490 if is_gguf || path.exists() {
1491 (
1492 Some(
1493 path.file_stem()
1494 .and_then(|s| s.to_str())
1495 .unwrap_or(m)
1496 .to_string(),
1497 ),
1498 Some(PathBuf::from(m)),
1499 )
1500 } else {
1501 (Some(m.to_string()), model_path.map(PathBuf::from))
1502 }
1503 } else {
1504 (None, model_path.map(PathBuf::from))
1505 };
1506
1507 cbtop::run(cbtop::CbtopConfig {
1508 model: resolved_model,
1509 attach: attach.map(String::from),
1510 model_path: resolved_model_path,
1511 headless,
1512 json,
1513 output: output.map(PathBuf::from),
1514 ci,
1515 throughput_threshold: throughput,
1516 brick_score_threshold: brick_score,
1517 warmup,
1518 iterations,
1519 speculative,
1520 speculation_k,
1521 draft_model_path: draft_model.map(PathBuf::from),
1522 concurrent,
1523 simulated,
1524 })
1525}
1526
1527#[allow(clippy::too_many_arguments)]
1529fn dispatch_showcase(
1530 auto_verify: bool,
1531 step: Option<&str>,
1532 tier: &str,
1533 model_dir: &Path,
1534 baseline: &str,
1535 zram: bool,
1536 runs: usize,
1537 gpu: bool,
1538 json: bool,
1539 verbose: bool,
1540 quiet: bool,
1541) -> Result<(), CliError> {
1542 let step = step.and_then(|s| match s {
1543 "import" => Some(showcase::ShowcaseStep::Import),
1544 "gguf" => Some(showcase::ShowcaseStep::GgufInference),
1545 "convert" => Some(showcase::ShowcaseStep::Convert),
1546 "apr" => Some(showcase::ShowcaseStep::AprInference),
1547 "bench" => Some(showcase::ShowcaseStep::Benchmark),
1548 "chat" => Some(showcase::ShowcaseStep::Chat),
1549 "visualize" => Some(showcase::ShowcaseStep::Visualize),
1550 "zram" => Some(showcase::ShowcaseStep::ZramDemo),
1551 "cuda" => Some(showcase::ShowcaseStep::CudaDemo),
1552 "brick" => Some(showcase::ShowcaseStep::BrickDemo),
1553 "all" => Some(showcase::ShowcaseStep::All),
1554 _ => None,
1555 });
1556
1557 let tier = match tier {
1558 "tiny" => showcase::ModelTier::Tiny,
1559 "small" => showcase::ModelTier::Small,
1560 "medium" => showcase::ModelTier::Medium,
1561 "large" => showcase::ModelTier::Large,
1562 _ => showcase::ModelTier::Small,
1563 };
1564
1565 let baselines: Vec<showcase::Baseline> = baseline
1566 .split(',')
1567 .filter_map(|b| match b.trim() {
1568 "llama-cpp" => Some(showcase::Baseline::LlamaCpp),
1569 "ollama" => Some(showcase::Baseline::Ollama),
1570 _ => None,
1571 })
1572 .collect();
1573
1574 let export_format = if json {
1575 showcase::ExportFormat::Json
1576 } else {
1577 showcase::ExportFormat::None
1578 };
1579
1580 let config = showcase::ShowcaseConfig {
1581 tier,
1582 model: tier.model_path().to_string(),
1583 quant: "Q4_K_M".to_string(),
1584 model_dir: model_dir.to_path_buf(),
1585 auto_verify,
1586 step,
1587 baselines,
1588 zram,
1589 bench_runs: runs,
1590 export_format,
1591 export_path: None,
1592 gpu,
1593 verbose,
1594 quiet,
1595 };
1596
1597 showcase::run(&config)
1598}
1599
1600#[allow(clippy::too_many_arguments)]
1602fn dispatch_profile(
1603 file: &Path,
1604 granular: bool,
1605 format: &str,
1606 focus: Option<&str>,
1607 detect_naive: bool,
1608 threshold: f64,
1609 compare_hf: Option<&str>,
1610 energy: bool,
1611 perf_grade: bool,
1612 callgraph: bool,
1613 fail_on_naive: bool,
1614 output: Option<&Path>,
1615 ci: bool,
1616 assert_throughput: Option<f64>,
1617 assert_p99: Option<f64>,
1618 assert_p50: Option<f64>,
1619 warmup: usize,
1620 measure: usize,
1621 tokens: usize,
1622 ollama: bool,
1623 no_gpu: bool,
1624 compare: Option<&Path>,
1625) -> Result<(), CliError> {
1626 let output_format = format.parse().unwrap_or(profile::OutputFormat::Human);
1627
1628 if ci || assert_throughput.is_some() || assert_p99.is_some() || assert_p50.is_some() {
1630 let assertions = profile::CiAssertions {
1631 min_throughput: assert_throughput,
1632 max_p99_ms: assert_p99,
1633 max_p50_ms: assert_p50,
1634 max_memory_mb: None,
1635 };
1636 match profile::run_ci(file, output_format, &assertions, warmup, measure) {
1637 Ok(true) => Ok(()),
1638 Ok(false) => {
1639 std::process::exit(1);
1640 }
1641 Err(e) => Err(e),
1642 }
1643 } else if let Some(compare_path) = compare {
1644 profile::run_cross_format_comparison(file, compare_path, warmup, measure, tokens, no_gpu)
1646 } else {
1647 let profile_focus = focus
1648 .and_then(|f| f.parse().ok())
1649 .unwrap_or(profile::ProfileFocus::All);
1650 profile::run(
1651 file,
1652 granular,
1653 output_format,
1654 profile_focus,
1655 detect_naive,
1656 threshold,
1657 compare_hf,
1658 energy,
1659 perf_grade,
1660 callgraph,
1661 fail_on_naive,
1662 output,
1663 tokens,
1664 ollama,
1665 no_gpu,
1666 )
1667 }
1668}
1669
1670#[allow(clippy::too_many_arguments)]
1672fn dispatch_run(
1673 source: &str,
1674 positional_prompt: Option<&String>,
1675 input: Option<&Path>,
1676 prompt: Option<&String>,
1677 max_tokens: usize,
1678 stream: bool,
1679 language: Option<&str>,
1680 task: Option<&str>,
1681 format: &str,
1682 no_gpu: bool,
1683 offline: bool,
1684 benchmark: bool,
1685 verbose: bool,
1686 trace: bool,
1687 trace_payload: bool,
1688 trace_steps: Option<&[String]>,
1689 trace_verbose: bool,
1690 trace_output: Option<PathBuf>,
1691 trace_level: &str,
1692 profile: bool,
1693 chat: bool,
1694) -> Result<(), CliError> {
1695 let effective_trace = trace || trace_payload;
1696 let effective_trace_level = if trace_payload {
1697 "payload"
1698 } else {
1699 trace_level
1700 };
1701 let merged_prompt = prompt.or(positional_prompt).cloned();
1702 let effective_prompt = if chat {
1703 merged_prompt
1704 .as_ref()
1705 .map(|p| format!("<|im_start|>user\n{p}<|im_end|>\n<|im_start|>assistant\n"))
1706 } else {
1707 merged_prompt
1708 };
1709
1710 run::run(
1711 source,
1712 input,
1713 effective_prompt.as_deref(),
1714 max_tokens,
1715 stream,
1716 language,
1717 task,
1718 format,
1719 no_gpu,
1720 offline,
1721 benchmark,
1722 verbose,
1723 effective_trace,
1724 trace_steps,
1725 trace_verbose,
1726 trace_output,
1727 effective_trace_level,
1728 profile,
1729 )
1730}
1731
1732#[allow(clippy::too_many_arguments)]
1734fn dispatch_serve(
1735 file: &Path,
1736 port: u16,
1737 host: &str,
1738 no_cors: bool,
1739 no_metrics: bool,
1740 no_gpu: bool,
1741 gpu: bool,
1742 batch: bool,
1743 trace: bool,
1744 trace_level: &str,
1745 profile: bool,
1746 verbose: bool,
1747) -> Result<(), CliError> {
1748 let config = serve::ServerConfig {
1749 port,
1750 host: host.to_owned(),
1751 cors: !no_cors,
1752 metrics: !no_metrics,
1753 no_gpu,
1754 gpu,
1755 batch,
1756 trace,
1757 trace_level: trace_level.to_owned(),
1758 profile,
1759 verbose,
1760 ..Default::default()
1761 };
1762 serve::run(file, &config)
1763}
1764
1765#[allow(clippy::too_many_arguments)]
1767fn dispatch_hex(
1768 file: &Path,
1769 tensor: Option<&str>,
1770 limit: usize,
1771 stats: bool,
1772 list: bool,
1773 json: bool,
1774 header: bool,
1775 blocks: bool,
1776 distribution: bool,
1777 contract: bool,
1778 entropy: bool,
1779 raw: bool,
1780 offset: &str,
1781 width: usize,
1782) -> Result<(), CliError> {
1783 let parsed_offset = hex::parse_hex_offset(offset).map_err(CliError::InvalidFormat)?;
1784 hex::run(&hex::HexOptions {
1785 file: file.to_path_buf(),
1786 tensor: tensor.map(String::from),
1787 limit,
1788 stats,
1789 list,
1790 json,
1791 header,
1792 blocks,
1793 distribution,
1794 contract,
1795 entropy,
1796 raw,
1797 offset: parsed_offset,
1798 width,
1799 })
1800}
1801
1802fn dispatch_rosetta(action: &RosettaCommands, global_json: bool) -> Result<(), CliError> {
1804 match action {
1805 RosettaCommands::Inspect {
1806 file,
1807 hexdump,
1808 json,
1809 } => rosetta::run_inspect(file, *hexdump, *json || global_json),
1810 RosettaCommands::Convert {
1811 source,
1812 target,
1813 quantize,
1814 verify,
1815 json,
1816 tokenizer,
1817 } => rosetta::run_convert(
1818 source,
1819 target,
1820 quantize.as_deref(),
1821 *verify,
1822 *json || global_json,
1823 tokenizer.as_deref(),
1824 ),
1825 RosettaCommands::Chain {
1826 source,
1827 formats,
1828 work_dir,
1829 json,
1830 } => rosetta::run_chain(source, formats, work_dir, *json || global_json),
1831 RosettaCommands::Verify {
1832 source,
1833 intermediate,
1834 tolerance,
1835 json,
1836 } => rosetta::run_verify(source, intermediate, *tolerance, *json || global_json),
1837 RosettaCommands::CompareInference {
1838 model_a,
1839 model_b,
1840 prompt,
1841 max_tokens,
1842 temperature,
1843 tolerance,
1844 json,
1845 } => rosetta::run_compare_inference(
1846 model_a,
1847 model_b,
1848 prompt,
1849 *max_tokens,
1850 *temperature,
1851 *tolerance,
1852 *json || global_json,
1853 ),
1854 RosettaCommands::DiffTensors {
1855 model_a,
1856 model_b,
1857 mismatches_only,
1858 show_values,
1859 filter,
1860 json,
1861 } => rosetta::run_diff_tensors(
1862 model_a,
1863 model_b,
1864 *mismatches_only,
1865 *show_values,
1866 filter.as_deref(),
1867 *json || global_json,
1868 ),
1869 RosettaCommands::Fingerprint {
1870 model,
1871 model_b,
1872 output,
1873 filter,
1874 verbose,
1875 json,
1876 } => rosetta::run_fingerprint(
1877 model,
1878 model_b.as_ref().map(std::path::PathBuf::as_path),
1879 output.as_ref().map(std::path::PathBuf::as_path),
1880 filter.as_deref(),
1881 *verbose,
1882 *json || global_json,
1883 ),
1884 RosettaCommands::ValidateStats {
1885 model,
1886 reference,
1887 fingerprints,
1888 threshold,
1889 strict,
1890 json,
1891 } => rosetta::run_validate_stats(
1892 model,
1893 reference.as_ref().map(std::path::PathBuf::as_path),
1894 fingerprints.as_ref().map(std::path::PathBuf::as_path),
1895 *threshold,
1896 *strict,
1897 *json || global_json,
1898 ),
1899 }
1900}
1901
1902pub fn execute_command(cli: &Cli) -> Result<(), CliError> {
1904 if !cli.skip_contract {
1906 let paths = extract_model_paths(&cli.command);
1907 validate_model_contract(&paths)?;
1908 }
1909
1910 dispatch_core_command(cli).unwrap_or_else(|| dispatch_extended_command(cli))
1911}
1912
1913#[allow(clippy::too_many_lines)]
1915fn dispatch_core_command(cli: &Cli) -> Option<Result<(), CliError>> {
1916 Some(match cli.command.as_ref() {
1917 Commands::Check { file, no_gpu } => commands::check::run(file, *no_gpu),
1918 Commands::Run {
1919 source,
1920 positional_prompt,
1921 input,
1922 prompt,
1923 max_tokens,
1924 stream,
1925 language,
1926 task,
1927 format,
1928 no_gpu,
1929 gpu: _,
1930 offline,
1931 benchmark,
1932 trace,
1933 trace_steps,
1934 trace_verbose,
1935 trace_output,
1936 trace_level,
1937 trace_payload,
1938 profile,
1939 chat,
1940 verbose,
1941 } => dispatch_run(
1942 source,
1943 positional_prompt.as_ref(),
1944 input.as_deref(),
1945 prompt.as_ref(),
1946 *max_tokens,
1947 *stream,
1948 language.as_deref(),
1949 task.as_deref(),
1950 format,
1951 *no_gpu,
1952 *offline,
1953 *benchmark,
1954 *verbose || cli.verbose,
1955 *trace,
1956 *trace_payload,
1957 trace_steps.as_deref(),
1958 *trace_verbose,
1959 trace_output.clone(),
1960 trace_level.as_str(),
1961 *profile,
1962 *chat,
1963 ),
1964
1965 Commands::Serve {
1966 file,
1967 port,
1968 host,
1969 no_cors,
1970 no_metrics,
1971 no_gpu,
1972 gpu,
1973 batch,
1974 trace,
1975 trace_level,
1976 profile,
1977 } => dispatch_serve(
1978 file,
1979 *port,
1980 host,
1981 *no_cors,
1982 *no_metrics,
1983 *no_gpu,
1984 *gpu,
1985 *batch,
1986 *trace,
1987 trace_level,
1988 *profile,
1989 cli.verbose,
1990 ),
1991
1992 Commands::Inspect {
1993 file,
1994 vocab,
1995 filters,
1996 weights,
1997 json,
1998 } => inspect::run(file, *vocab, *filters, *weights, *json || cli.json),
1999
2000 Commands::Debug {
2001 file,
2002 drama,
2003 hex,
2004 strings,
2005 limit,
2006 } => debug::run(file, *drama, *hex, *strings, *limit),
2007
2008 Commands::Validate {
2009 file,
2010 quality,
2011 strict,
2012 min_score,
2013 } => validate::run(file, *quality, *strict, *min_score),
2014
2015 Commands::Diff {
2016 file1,
2017 file2,
2018 weights,
2019 values,
2020 filter,
2021 limit,
2022 transpose_aware,
2023 json,
2024 } => diff::run(
2025 file1,
2026 file2,
2027 *weights,
2028 *values,
2029 filter.as_deref(),
2030 *limit,
2031 *transpose_aware,
2032 *json || cli.json,
2033 ),
2034
2035 Commands::Tensors {
2036 file,
2037 stats,
2038 filter,
2039 limit,
2040 json,
2041 } => tensors::run(file, *stats, filter.as_deref(), *json || cli.json, *limit),
2042
2043 Commands::Trace {
2044 file,
2045 layer,
2046 reference,
2047 json,
2048 verbose,
2049 payload,
2050 diff,
2051 interactive,
2052 } => trace::run(
2053 file,
2054 layer.as_deref(),
2055 reference.as_deref(),
2056 *json || cli.json,
2057 *verbose || cli.verbose,
2058 *payload,
2059 *diff,
2060 *interactive,
2061 ),
2062
2063 Commands::Lint { file } => lint::run(file),
2064 Commands::Explain { code, file, tensor } => {
2065 explain::run(code.clone(), file.clone(), tensor.clone())
2066 }
2067 Commands::Canary { command } => canary::run(command.clone()),
2068 Commands::Export {
2069 file,
2070 format,
2071 output,
2072 quantize,
2073 } => export::run(file, format, output, quantize.as_deref()),
2074 Commands::Import {
2075 source,
2076 output,
2077 arch,
2078 quantize,
2079 strict,
2080 preserve_q4k,
2081 tokenizer,
2082 enforce_provenance,
2083 } => import::run(
2084 source,
2085 output.as_deref(),
2086 Some(arch.as_str()),
2087 quantize.as_deref(),
2088 *strict,
2089 *preserve_q4k,
2090 tokenizer.as_ref(),
2091 *enforce_provenance,
2092 ),
2093 Commands::Pull { model_ref, force } => pull::run(model_ref, *force),
2094 Commands::List => pull::list(),
2095 Commands::Rm { model_ref } => pull::remove(model_ref),
2096 Commands::Convert {
2097 file,
2098 quantize,
2099 compress,
2100 output,
2101 force,
2102 } => convert::run(
2103 file,
2104 quantize.as_deref(),
2105 compress.as_deref(),
2106 output,
2107 *force,
2108 ),
2109 Commands::Merge {
2110 files,
2111 strategy,
2112 output,
2113 weights,
2114 } => merge::run(files, strategy, output, weights.clone()),
2115 Commands::Tui { file } => tui::run(file.clone()),
2116 _ => return None,
2117 })
2118}
2119
2120#[allow(clippy::too_many_lines)]
2122fn dispatch_extended_command(cli: &Cli) -> Result<(), CliError> {
2123 match cli.command.as_ref() {
2124 Commands::Cbtop {
2125 model,
2126 attach,
2127 model_path,
2128 headless,
2129 json,
2130 output,
2131 ci,
2132 throughput,
2133 brick_score,
2134 warmup,
2135 iterations,
2136 speculative,
2137 speculation_k,
2138 draft_model,
2139 concurrent,
2140 simulated,
2141 } => dispatch_cbtop(
2142 model.as_deref(),
2143 attach.as_deref(),
2144 model_path.as_deref(),
2145 *headless,
2146 *json,
2147 output.as_deref(),
2148 *ci,
2149 *throughput,
2150 *brick_score,
2151 *warmup,
2152 *iterations,
2153 *speculative,
2154 *speculation_k,
2155 draft_model.as_deref(),
2156 *concurrent,
2157 *simulated,
2158 ),
2159
2160 Commands::Probar {
2161 file,
2162 output,
2163 format,
2164 golden,
2165 layer,
2166 } => probar::run(
2167 file,
2168 output,
2169 format.parse().unwrap_or(probar::ExportFormat::Both),
2170 golden.as_deref(),
2171 layer.as_deref(),
2172 ),
2173
2174 Commands::CompareHf {
2175 file,
2176 hf,
2177 tensor,
2178 threshold,
2179 json,
2180 } => compare_hf::run(file, hf, tensor.as_deref(), *threshold, *json || cli.json),
2181
2182 Commands::Hex {
2183 file,
2184 tensor,
2185 limit,
2186 stats,
2187 list,
2188 json,
2189 header,
2190 blocks,
2191 distribution,
2192 contract,
2193 entropy,
2194 raw,
2195 offset,
2196 width,
2197 } => dispatch_hex(
2198 file,
2199 tensor.as_deref(),
2200 *limit,
2201 *stats,
2202 *list,
2203 *json || cli.json,
2204 *header,
2205 *blocks,
2206 *distribution,
2207 *contract,
2208 *entropy,
2209 *raw,
2210 offset,
2211 *width,
2212 ),
2213
2214 Commands::Tree {
2215 file,
2216 filter,
2217 format,
2218 sizes,
2219 depth,
2220 } => tree::run(
2221 file,
2222 filter.as_deref(),
2223 format.parse().unwrap_or(tree::TreeFormat::Ascii),
2224 *sizes,
2225 *depth,
2226 ),
2227
2228 Commands::Flow {
2229 file,
2230 layer,
2231 component,
2232 verbose,
2233 } => flow::run(
2234 file,
2235 layer.as_deref(),
2236 component.parse().unwrap_or(flow::FlowComponent::Full),
2237 *verbose || cli.verbose,
2238 ),
2239
2240 Commands::Chat {
2241 file,
2242 temperature,
2243 top_p,
2244 max_tokens,
2245 system,
2246 inspect,
2247 no_gpu,
2248 gpu: _,
2249 trace,
2250 trace_steps,
2251 trace_verbose,
2252 trace_output,
2253 trace_level,
2254 profile,
2255 } => chat::run(
2256 file,
2257 *temperature,
2258 *top_p,
2259 *max_tokens,
2260 system.as_deref(),
2261 *inspect,
2262 *no_gpu,
2263 *trace,
2264 trace_steps.as_deref(),
2265 *trace_verbose,
2266 trace_output.clone(),
2267 trace_level.as_str(),
2268 *profile,
2269 ),
2270
2271 Commands::Bench {
2272 file,
2273 warmup,
2274 iterations,
2275 max_tokens,
2276 prompt,
2277 fast,
2278 brick,
2279 } => bench::run(
2280 file,
2281 *warmup,
2282 *iterations,
2283 *max_tokens,
2284 prompt.as_deref(),
2285 *fast,
2286 brick.as_deref(),
2287 ),
2288
2289 Commands::Eval {
2290 file,
2291 dataset,
2292 text,
2293 max_tokens,
2294 threshold,
2295 } => eval::run(
2296 file,
2297 dataset,
2298 text.as_deref(),
2299 Some(*max_tokens),
2300 Some(*threshold),
2301 ),
2302
2303 Commands::Profile {
2304 file,
2305 granular,
2306 format,
2307 focus,
2308 detect_naive,
2309 threshold,
2310 compare_hf,
2311 energy,
2312 perf_grade,
2313 callgraph,
2314 fail_on_naive,
2315 output,
2316 ci,
2317 assert_throughput,
2318 assert_p99,
2319 assert_p50,
2320 warmup,
2321 measure,
2322 tokens,
2323 ollama,
2324 no_gpu,
2325 compare,
2326 } => dispatch_profile(
2327 file,
2328 *granular,
2329 format,
2330 focus.as_deref(),
2331 *detect_naive,
2332 *threshold,
2333 compare_hf.as_deref(),
2334 *energy,
2335 *perf_grade,
2336 *callgraph,
2337 *fail_on_naive,
2338 output.as_deref(),
2339 *ci,
2340 *assert_throughput,
2341 *assert_p99,
2342 *assert_p50,
2343 *warmup,
2344 *measure,
2345 *tokens,
2346 *ollama,
2347 *no_gpu,
2348 compare.as_deref(),
2349 ),
2350
2351 Commands::Qa {
2352 file,
2353 assert_tps,
2354 assert_speedup,
2355 assert_gpu_speedup,
2356 skip_golden,
2357 skip_throughput,
2358 skip_ollama,
2359 skip_gpu_speedup,
2360 skip_contract,
2361 skip_format_parity,
2362 skip_ptx_parity,
2363 safetensors_path,
2364 iterations,
2365 warmup,
2366 max_tokens,
2367 json,
2368 verbose,
2369 min_executed,
2370 previous_report,
2371 regression_threshold,
2372 skip_gpu_state,
2373 skip_metadata,
2374 } => qa::run(
2375 file,
2376 *assert_tps,
2377 *assert_speedup,
2378 *assert_gpu_speedup,
2379 *skip_golden,
2380 *skip_throughput,
2381 *skip_ollama,
2382 *skip_gpu_speedup,
2383 *skip_contract,
2384 *skip_format_parity,
2385 *skip_ptx_parity,
2386 safetensors_path.clone(),
2387 *iterations,
2388 *warmup,
2389 *max_tokens,
2390 *json || cli.json,
2391 *verbose || cli.verbose,
2392 *min_executed,
2393 previous_report.clone(),
2394 *regression_threshold,
2395 *skip_gpu_state,
2396 *skip_metadata,
2397 ),
2398
2399 Commands::Parity {
2400 file,
2401 prompt,
2402 assert,
2403 } => commands::parity::run(file, prompt, *assert, cli.verbose),
2404
2405 Commands::PtxMap {
2406 file,
2407 kernel,
2408 reverse,
2409 json,
2410 verbose,
2411 prefill,
2412 } => commands::ptx_map::run(
2413 file,
2414 kernel.as_deref(),
2415 reverse.as_deref(),
2416 *json || cli.json,
2417 *verbose || cli.verbose,
2418 *prefill,
2419 ),
2420
2421 Commands::Ptx {
2422 file,
2423 kernel,
2424 strict,
2425 bugs,
2426 json,
2427 verbose,
2428 } => ptx_explain::run(
2429 file.as_deref(),
2430 kernel.as_deref(),
2431 *strict,
2432 *bugs,
2433 *json || cli.json,
2434 *verbose || cli.verbose,
2435 ),
2436
2437 Commands::Tune {
2438 file,
2439 method,
2440 rank,
2441 vram,
2442 plan,
2443 model,
2444 freeze_base,
2445 train_data,
2446 json,
2447 } => tune::run(
2448 file.as_deref(),
2449 method.parse().unwrap_or(tune::TuneMethod::Auto),
2450 *rank,
2451 *vram,
2452 *plan,
2453 model.as_deref(),
2454 *freeze_base,
2455 train_data.as_deref(),
2456 *json || cli.json,
2457 ),
2458
2459 Commands::Showcase {
2460 auto_verify,
2461 step,
2462 tier,
2463 model_dir,
2464 baseline,
2465 zram,
2466 runs,
2467 gpu,
2468 json,
2469 verbose,
2470 quiet,
2471 } => dispatch_showcase(
2472 *auto_verify,
2473 step.as_deref(),
2474 tier,
2475 model_dir,
2476 baseline,
2477 *zram,
2478 *runs,
2479 *gpu,
2480 *json,
2481 *verbose,
2482 *quiet,
2483 ),
2484
2485 Commands::Rosetta { action } => dispatch_rosetta(action, cli.json),
2486
2487 Commands::Publish {
2488 directory,
2489 repo_id,
2490 model_name,
2491 license,
2492 pipeline_tag,
2493 library_name,
2494 tags,
2495 message,
2496 dry_run,
2497 } => publish::execute(
2498 directory,
2499 repo_id,
2500 model_name.as_deref(),
2501 license,
2502 pipeline_tag,
2503 library_name.as_deref(),
2504 tags.as_ref().map_or(&[], std::vec::Vec::as_slice),
2505 message.as_deref(),
2506 *dry_run,
2507 cli.verbose,
2508 ),
2509
2510 Commands::Oracle {
2511 source,
2512 family,
2513 size,
2514 compliance,
2515 tensors,
2516 stats,
2517 explain,
2518 kernels,
2519 validate,
2520 full,
2521 } => oracle::run(
2522 source.as_ref(),
2523 family.as_ref(),
2524 size.as_ref(),
2525 *compliance,
2526 *tensors,
2527 cli.json,
2528 cli.verbose,
2529 cli.offline,
2530 oracle::OracleFlags {
2531 stats: *stats,
2532 explain: *explain,
2533 kernels: *kernels,
2534 validate: *validate,
2535 full: *full,
2536 },
2537 ),
2538
2539 _ => unreachable!("dispatch_core_command handles all remaining variants"),
2541 }
2542}
2543
2544#[cfg(test)]
2549mod tests {
2550 use super::*;
2551
2552 fn parse_cli(args: Vec<&'static str>) -> Result<Cli, clap::error::Error> {
2556 std::thread::Builder::new()
2557 .stack_size(16 * 1024 * 1024)
2558 .spawn(move || Cli::try_parse_from(args))
2559 .expect("spawn thread")
2560 .join()
2561 .expect("join thread")
2562 }
2563
2564 #[test]
2566 fn test_cli_parsing_valid() {
2567 use clap::CommandFactory;
2568 std::thread::Builder::new()
2569 .stack_size(16 * 1024 * 1024)
2570 .spawn(|| Cli::command().debug_assert())
2571 .expect("spawn")
2572 .join()
2573 .expect("join");
2574 }
2575
2576 #[test]
2578 fn test_parse_inspect_command() {
2579 let args = vec!["apr", "inspect", "model.apr"];
2580 let cli = parse_cli(args).expect("Failed to parse");
2581 match *cli.command {
2582 Commands::Inspect { file, .. } => {
2583 assert_eq!(file, PathBuf::from("model.apr"));
2584 }
2585 _ => panic!("Expected Inspect command"),
2586 }
2587 }
2588
2589 #[test]
2591 fn test_parse_inspect_with_flags() {
2592 let args = vec!["apr", "inspect", "model.apr", "--vocab", "--json"];
2593 let cli = parse_cli(args).expect("Failed to parse");
2594 match *cli.command {
2595 Commands::Inspect {
2596 file, vocab, json, ..
2597 } => {
2598 assert_eq!(file, PathBuf::from("model.apr"));
2599 assert!(vocab);
2600 assert!(json);
2601 }
2602 _ => panic!("Expected Inspect command"),
2603 }
2604 }
2605
2606 #[test]
2608 fn test_parse_serve_command() {
2609 let args = vec!["apr", "serve", "model.apr", "--port", "3000"];
2610 let cli = parse_cli(args).expect("Failed to parse");
2611 match *cli.command {
2612 Commands::Serve { file, port, .. } => {
2613 assert_eq!(file, PathBuf::from("model.apr"));
2614 assert_eq!(port, 3000);
2615 }
2616 _ => panic!("Expected Serve command"),
2617 }
2618 }
2619
2620 #[test]
2622 fn test_parse_run_command() {
2623 let args = vec![
2624 "apr",
2625 "run",
2626 "hf://openai/whisper-tiny",
2627 "--prompt",
2628 "Hello",
2629 "--max-tokens",
2630 "64",
2631 ];
2632 let cli = parse_cli(args).expect("Failed to parse");
2633 match *cli.command {
2634 Commands::Run {
2635 source,
2636 prompt,
2637 max_tokens,
2638 ..
2639 } => {
2640 assert_eq!(source, "hf://openai/whisper-tiny");
2641 assert_eq!(prompt, Some("Hello".to_string()));
2642 assert_eq!(max_tokens, 64);
2643 }
2644 _ => panic!("Expected Run command"),
2645 }
2646 }
2647
2648 #[test]
2650 fn test_parse_chat_command() {
2651 let args = vec![
2652 "apr",
2653 "chat",
2654 "model.gguf",
2655 "--temperature",
2656 "0.5",
2657 "--top-p",
2658 "0.95",
2659 ];
2660 let cli = parse_cli(args).expect("Failed to parse");
2661 match *cli.command {
2662 Commands::Chat {
2663 file,
2664 temperature,
2665 top_p,
2666 ..
2667 } => {
2668 assert_eq!(file, PathBuf::from("model.gguf"));
2669 assert!((temperature - 0.5).abs() < f32::EPSILON);
2670 assert!((top_p - 0.95).abs() < f32::EPSILON);
2671 }
2672 _ => panic!("Expected Chat command"),
2673 }
2674 }
2675
2676 #[test]
2678 fn test_parse_validate_with_quality() {
2679 let args = vec!["apr", "validate", "model.apr", "--quality", "--strict"];
2680 let cli = parse_cli(args).expect("Failed to parse");
2681 match *cli.command {
2682 Commands::Validate {
2683 file,
2684 quality,
2685 strict,
2686 ..
2687 } => {
2688 assert_eq!(file, PathBuf::from("model.apr"));
2689 assert!(quality);
2690 assert!(strict);
2691 }
2692 _ => panic!("Expected Validate command"),
2693 }
2694 }
2695
2696 #[test]
2698 fn test_parse_diff_command() {
2699 let args = vec!["apr", "diff", "model1.apr", "model2.apr", "--weights"];
2700 let cli = parse_cli(args).expect("Failed to parse");
2701 match *cli.command {
2702 Commands::Diff {
2703 file1,
2704 file2,
2705 weights,
2706 ..
2707 } => {
2708 assert_eq!(file1, PathBuf::from("model1.apr"));
2709 assert_eq!(file2, PathBuf::from("model2.apr"));
2710 assert!(weights);
2711 }
2712 _ => panic!("Expected Diff command"),
2713 }
2714 }
2715
2716 #[test]
2718 fn test_parse_bench_command() {
2719 let args = vec![
2720 "apr",
2721 "bench",
2722 "model.gguf",
2723 "--warmup",
2724 "5",
2725 "--iterations",
2726 "10",
2727 ];
2728 let cli = parse_cli(args).expect("Failed to parse");
2729 match *cli.command {
2730 Commands::Bench {
2731 file,
2732 warmup,
2733 iterations,
2734 ..
2735 } => {
2736 assert_eq!(file, PathBuf::from("model.gguf"));
2737 assert_eq!(warmup, 5);
2738 assert_eq!(iterations, 10);
2739 }
2740 _ => panic!("Expected Bench command"),
2741 }
2742 }
2743
2744 #[test]
2746 fn test_parse_cbtop_ci_mode() {
2747 let args = vec![
2748 "apr",
2749 "cbtop",
2750 "--headless",
2751 "--ci",
2752 "--throughput",
2753 "100.0",
2754 "--brick-score",
2755 "90",
2756 ];
2757 let cli = parse_cli(args).expect("Failed to parse");
2758 match *cli.command {
2759 Commands::Cbtop {
2760 headless,
2761 ci,
2762 throughput,
2763 brick_score,
2764 ..
2765 } => {
2766 assert!(headless);
2767 assert!(ci);
2768 assert_eq!(throughput, Some(100.0));
2769 assert_eq!(brick_score, Some(90));
2770 }
2771 _ => panic!("Expected Cbtop command"),
2772 }
2773 }
2774
2775 #[test]
2777 fn test_parse_qa_command() {
2778 let args = vec![
2779 "apr",
2780 "qa",
2781 "model.gguf",
2782 "--assert-tps",
2783 "50.0",
2784 "--skip-ollama",
2785 ];
2786 let cli = parse_cli(args).expect("Failed to parse");
2787 match *cli.command {
2788 Commands::Qa {
2789 file,
2790 assert_tps,
2791 skip_ollama,
2792 ..
2793 } => {
2794 assert_eq!(file, PathBuf::from("model.gguf"));
2795 assert_eq!(assert_tps, Some(50.0));
2796 assert!(skip_ollama);
2797 }
2798 _ => panic!("Expected Qa command"),
2799 }
2800 }
2801
2802 #[test]
2804 fn test_global_verbose_flag() {
2805 let args = vec!["apr", "--verbose", "inspect", "model.apr"];
2806 let cli = parse_cli(args).expect("Failed to parse");
2807 assert!(cli.verbose);
2808 }
2809
2810 #[test]
2812 fn test_global_json_flag() {
2813 let args = vec!["apr", "--json", "inspect", "model.apr"];
2814 let cli = parse_cli(args).expect("Failed to parse");
2815 assert!(cli.json);
2816 }
2817
2818 #[test]
2820 fn test_parse_list_command() {
2821 let args = vec!["apr", "list"];
2822 let cli = parse_cli(args).expect("Failed to parse");
2823 assert!(matches!(*cli.command, Commands::List));
2824 }
2825
2826 #[test]
2828 fn test_parse_ls_alias() {
2829 let args = vec!["apr", "ls"];
2830 let cli = parse_cli(args).expect("Failed to parse");
2831 assert!(matches!(*cli.command, Commands::List));
2832 }
2833
2834 #[test]
2836 fn test_parse_rm_command() {
2837 let args = vec!["apr", "rm", "model-name"];
2838 let cli = parse_cli(args).expect("Failed to parse");
2839 match *cli.command {
2840 Commands::Rm { model_ref } => {
2841 assert_eq!(model_ref, "model-name");
2842 }
2843 _ => panic!("Expected Rm command"),
2844 }
2845 }
2846
2847 #[test]
2849 fn test_invalid_command() {
2850 let args = vec!["apr", "invalid-command"];
2851 let result = parse_cli(args);
2852 assert!(result.is_err());
2853 }
2854
2855 #[test]
2857 fn test_missing_required_arg() {
2858 let args = vec!["apr", "inspect"]; let result = parse_cli(args);
2860 assert!(result.is_err());
2861 }
2862
2863 #[test]
2865 fn test_parse_merge_command() {
2866 let args = vec![
2867 "apr",
2868 "merge",
2869 "model1.apr",
2870 "model2.apr",
2871 "--strategy",
2872 "weighted",
2873 "--weights",
2874 "0.7,0.3",
2875 "-o",
2876 "merged.apr",
2877 ];
2878 let cli = parse_cli(args).expect("Failed to parse");
2879 match *cli.command {
2880 Commands::Merge {
2881 files,
2882 strategy,
2883 output,
2884 weights,
2885 } => {
2886 assert_eq!(files.len(), 2);
2887 assert_eq!(strategy, "weighted");
2888 assert_eq!(output, PathBuf::from("merged.apr"));
2889 assert_eq!(weights, Some(vec![0.7, 0.3]));
2890 }
2891 _ => panic!("Expected Merge command"),
2892 }
2893 }
2894
2895 #[test]
2897 fn test_parse_showcase_command() {
2898 let args = vec![
2899 "apr",
2900 "showcase",
2901 "--tier",
2902 "medium",
2903 "--gpu",
2904 "--auto-verify",
2905 ];
2906 let cli = parse_cli(args).expect("Failed to parse");
2907 match *cli.command {
2908 Commands::Showcase {
2909 tier,
2910 gpu,
2911 auto_verify,
2912 ..
2913 } => {
2914 assert_eq!(tier, "medium");
2915 assert!(gpu);
2916 assert!(auto_verify);
2917 }
2918 _ => panic!("Expected Showcase command"),
2919 }
2920 }
2921
2922 #[test]
2924 fn test_parse_profile_command() {
2925 let args = vec![
2926 "apr",
2927 "profile",
2928 "model.apr",
2929 "--granular",
2930 "--detect-naive",
2931 "--fail-on-naive",
2932 ];
2933 let cli = parse_cli(args).expect("Failed to parse");
2934 match *cli.command {
2935 Commands::Profile {
2936 file,
2937 granular,
2938 detect_naive,
2939 fail_on_naive,
2940 ..
2941 } => {
2942 assert_eq!(file, PathBuf::from("model.apr"));
2943 assert!(granular);
2944 assert!(detect_naive);
2945 assert!(fail_on_naive);
2946 }
2947 _ => panic!("Expected Profile command"),
2948 }
2949 }
2950
2951 #[test]
2953 fn test_parse_profile_ci_mode() {
2954 let args = vec![
2955 "apr",
2956 "profile",
2957 "model.gguf",
2958 "--ci",
2959 "--assert-throughput",
2960 "100",
2961 "--assert-p99",
2962 "50",
2963 "--format",
2964 "json",
2965 ];
2966 let cli = parse_cli(args).expect("Failed to parse");
2967 match *cli.command {
2968 Commands::Profile {
2969 file,
2970 ci,
2971 assert_throughput,
2972 assert_p99,
2973 format,
2974 ..
2975 } => {
2976 assert_eq!(file, PathBuf::from("model.gguf"));
2977 assert!(ci);
2978 assert_eq!(assert_throughput, Some(100.0));
2979 assert_eq!(assert_p99, Some(50.0));
2980 assert_eq!(format, "json");
2981 }
2982 _ => panic!("Expected Profile command"),
2983 }
2984 }
2985
2986 #[test]
2988 fn test_parse_rosetta_inspect() {
2989 let args = vec!["apr", "rosetta", "inspect", "model.gguf", "--json"];
2990 let cli = parse_cli(args).expect("Failed to parse");
2991 match *cli.command {
2992 Commands::Rosetta { action } => match action {
2993 RosettaCommands::Inspect { file, json, .. } => {
2994 assert_eq!(file, PathBuf::from("model.gguf"));
2995 assert!(json);
2996 }
2997 _ => panic!("Expected Inspect subcommand"),
2998 },
2999 _ => panic!("Expected Rosetta command"),
3000 }
3001 }
3002
3003 #[test]
3005 fn test_parse_rosetta_convert() {
3006 let args = vec![
3007 "apr",
3008 "rosetta",
3009 "convert",
3010 "model.gguf",
3011 "model.safetensors",
3012 "--verify",
3013 ];
3014 let cli = parse_cli(args).expect("Failed to parse");
3015 match *cli.command {
3016 Commands::Rosetta { action } => match action {
3017 RosettaCommands::Convert {
3018 source,
3019 target,
3020 verify,
3021 ..
3022 } => {
3023 assert_eq!(source, PathBuf::from("model.gguf"));
3024 assert_eq!(target, PathBuf::from("model.safetensors"));
3025 assert!(verify);
3026 }
3027 _ => panic!("Expected Convert subcommand"),
3028 },
3029 _ => panic!("Expected Rosetta command"),
3030 }
3031 }
3032
3033 #[test]
3035 fn test_parse_rosetta_chain() {
3036 let args = vec![
3037 "apr",
3038 "rosetta",
3039 "chain",
3040 "model.gguf",
3041 "safetensors",
3042 "apr",
3043 "--work-dir",
3044 "/tmp/rosetta",
3045 ];
3046 let cli = parse_cli(args).expect("Failed to parse");
3047 match *cli.command {
3048 Commands::Rosetta { action } => match action {
3049 RosettaCommands::Chain {
3050 source,
3051 formats,
3052 work_dir,
3053 ..
3054 } => {
3055 assert_eq!(source, PathBuf::from("model.gguf"));
3056 assert_eq!(formats, vec!["safetensors", "apr"]);
3057 assert_eq!(work_dir, PathBuf::from("/tmp/rosetta"));
3058 }
3059 _ => panic!("Expected Chain subcommand"),
3060 },
3061 _ => panic!("Expected Rosetta command"),
3062 }
3063 }
3064
3065 #[test]
3067 fn test_parse_rosetta_verify() {
3068 let args = vec![
3069 "apr",
3070 "rosetta",
3071 "verify",
3072 "model.apr",
3073 "--intermediate",
3074 "gguf",
3075 "--tolerance",
3076 "1e-4",
3077 ];
3078 let cli = parse_cli(args).expect("Failed to parse");
3079 match *cli.command {
3080 Commands::Rosetta { action } => match action {
3081 RosettaCommands::Verify {
3082 source,
3083 intermediate,
3084 tolerance,
3085 ..
3086 } => {
3087 assert_eq!(source, PathBuf::from("model.apr"));
3088 assert_eq!(intermediate, "gguf");
3089 assert!((tolerance - 1e-4).abs() < f32::EPSILON);
3090 }
3091 _ => panic!("Expected Verify subcommand"),
3092 },
3093 _ => panic!("Expected Rosetta command"),
3094 }
3095 }
3096
3097 #[test]
3103 fn test_parse_skip_contract_flag() {
3104 let args = vec!["apr", "--skip-contract", "inspect", "model.apr"];
3105 let cli = parse_cli(args).expect("Failed to parse");
3106 assert!(cli.skip_contract);
3107 }
3108
3109 #[test]
3111 fn test_skip_contract_default_false() {
3112 let args = vec!["apr", "inspect", "model.apr"];
3113 let cli = parse_cli(args).expect("Failed to parse");
3114 assert!(!cli.skip_contract);
3115 }
3116
3117 #[test]
3119 fn test_extract_paths_diagnostic_exempt() {
3120 let diagnostic_commands = vec![
3122 Commands::Inspect {
3123 file: PathBuf::from("m.apr"),
3124 vocab: false,
3125 filters: false,
3126 weights: false,
3127 json: false,
3128 },
3129 Commands::Debug {
3130 file: PathBuf::from("m.apr"),
3131 drama: false,
3132 hex: false,
3133 strings: false,
3134 limit: 256,
3135 },
3136 Commands::Validate {
3137 file: PathBuf::from("m.apr"),
3138 quality: false,
3139 strict: false,
3140 min_score: None,
3141 },
3142 Commands::Tensors {
3143 file: PathBuf::from("m.apr"),
3144 stats: false,
3145 filter: None,
3146 limit: 0,
3147 json: false,
3148 },
3149 Commands::Lint {
3150 file: PathBuf::from("m.apr"),
3151 },
3152 Commands::Qa {
3153 file: PathBuf::from("m.apr"),
3154 assert_tps: None,
3155 assert_speedup: None,
3156 assert_gpu_speedup: None,
3157 skip_golden: false,
3158 skip_throughput: false,
3159 skip_ollama: false,
3160 skip_gpu_speedup: false,
3161 skip_contract: false,
3162 skip_format_parity: false,
3163 skip_ptx_parity: false,
3164 safetensors_path: None,
3165 iterations: 10,
3166 warmup: 3,
3167 max_tokens: 32,
3168 json: false,
3169 verbose: false,
3170 min_executed: None,
3171 previous_report: None,
3172 regression_threshold: None,
3173 skip_gpu_state: false,
3174 skip_metadata: false,
3175 },
3176 Commands::Hex {
3177 file: PathBuf::from("m.apr"),
3178 tensor: None,
3179 limit: 64,
3180 stats: false,
3181 list: false,
3182 json: false,
3183 header: false,
3184 blocks: false,
3185 distribution: false,
3186 contract: false,
3187 entropy: false,
3188 raw: false,
3189 offset: "0".to_string(),
3190 width: 16,
3191 },
3192 Commands::Tree {
3193 file: PathBuf::from("m.apr"),
3194 filter: None,
3195 format: "ascii".to_string(),
3196 sizes: false,
3197 depth: None,
3198 },
3199 Commands::Flow {
3200 file: PathBuf::from("m.apr"),
3201 layer: None,
3202 component: "full".to_string(),
3203 verbose: false,
3204 },
3205 Commands::Explain {
3206 code: None,
3207 file: None,
3208 tensor: None,
3209 },
3210 Commands::List,
3211 ];
3212 for cmd in &diagnostic_commands {
3213 let paths = extract_model_paths(cmd);
3214 assert!(
3215 paths.is_empty(),
3216 "Diagnostic command should be exempt: {cmd:?}"
3217 );
3218 }
3219 }
3220
3221 #[test]
3223 fn test_extract_paths_action_commands() {
3224 let serve_cmd = Commands::Serve {
3225 file: PathBuf::from("model.gguf"),
3226 port: 8080,
3227 host: "127.0.0.1".to_string(),
3228 no_cors: false,
3229 no_metrics: false,
3230 no_gpu: false,
3231 gpu: false,
3232 batch: false,
3233 trace: false,
3234 trace_level: "basic".to_string(),
3235 profile: false,
3236 };
3237 let paths = extract_model_paths(&serve_cmd);
3238 assert_eq!(paths, vec![PathBuf::from("model.gguf")]);
3239
3240 let bench_cmd = Commands::Bench {
3241 file: PathBuf::from("model.apr"),
3242 warmup: 3,
3243 iterations: 5,
3244 max_tokens: 32,
3245 prompt: None,
3246 fast: false,
3247 brick: None,
3248 };
3249 let paths = extract_model_paths(&bench_cmd);
3250 assert_eq!(paths, vec![PathBuf::from("model.apr")]);
3251 }
3252
3253 #[test]
3255 fn test_extract_paths_run_hf_url() {
3256 let cmd = Commands::Run {
3257 source: "hf://org/repo".to_string(),
3258 positional_prompt: None,
3259 input: None,
3260 prompt: None,
3261 max_tokens: 32,
3262 stream: false,
3263 language: None,
3264 task: None,
3265 format: "text".to_string(),
3266 no_gpu: false,
3267 gpu: false,
3268 offline: false,
3269 benchmark: false,
3270 trace: false,
3271 trace_steps: None,
3272 trace_verbose: false,
3273 trace_output: None,
3274 trace_level: "basic".to_string(),
3275 trace_payload: false,
3276 profile: false,
3277 chat: false,
3278 verbose: false,
3279 };
3280 let paths = extract_model_paths(&cmd);
3281 assert!(
3282 paths.is_empty(),
3283 "hf:// URLs should not be validated locally"
3284 );
3285 }
3286
3287 #[test]
3289 fn test_extract_paths_merge_multiple() {
3290 let cmd = Commands::Merge {
3291 files: vec![
3292 PathBuf::from("a.apr"),
3293 PathBuf::from("b.apr"),
3294 PathBuf::from("c.apr"),
3295 ],
3296 strategy: "average".to_string(),
3297 output: PathBuf::from("merged.apr"),
3298 weights: None,
3299 };
3300 let paths = extract_model_paths(&cmd);
3301 assert_eq!(paths.len(), 3);
3302 }
3303
3304 #[test]
3306 fn test_validate_contract_nonexistent_skipped() {
3307 let paths = vec![PathBuf::from("nonexistent_model_xyz.apr")];
3308 let result = validate_model_contract(&paths);
3309 assert!(result.is_ok(), "Non-existent paths should be skipped");
3310 }
3311
3312 #[test]
3314 fn test_validate_contract_empty_paths() {
3315 let result = validate_model_contract(&[]);
3316 assert!(result.is_ok());
3317 }
3318
3319 #[test]
3325 fn test_parse_publish_command() {
3326 let args = vec![
3327 "apr",
3328 "publish",
3329 "/tmp/models",
3330 "paiml/whisper-apr-tiny",
3331 "--model-name",
3332 "Whisper Tiny",
3333 "--license",
3334 "apache-2.0",
3335 "--pipeline-tag",
3336 "automatic-speech-recognition",
3337 "--library-name",
3338 "whisper-apr",
3339 "--tags",
3340 "whisper,tiny,asr",
3341 "--message",
3342 "Initial release",
3343 "--dry-run",
3344 ];
3345 let cli = parse_cli(args).expect("Failed to parse");
3346 match *cli.command {
3347 Commands::Publish {
3348 directory,
3349 repo_id,
3350 model_name,
3351 license,
3352 pipeline_tag,
3353 library_name,
3354 tags,
3355 message,
3356 dry_run,
3357 } => {
3358 assert_eq!(directory, PathBuf::from("/tmp/models"));
3359 assert_eq!(repo_id, "paiml/whisper-apr-tiny");
3360 assert_eq!(model_name, Some("Whisper Tiny".to_string()));
3361 assert_eq!(license, "apache-2.0");
3362 assert_eq!(pipeline_tag, "automatic-speech-recognition");
3363 assert_eq!(library_name, Some("whisper-apr".to_string()));
3364 assert_eq!(
3365 tags,
3366 Some(vec![
3367 "whisper".to_string(),
3368 "tiny".to_string(),
3369 "asr".to_string()
3370 ])
3371 );
3372 assert_eq!(message, Some("Initial release".to_string()));
3373 assert!(dry_run);
3374 }
3375 _ => panic!("Expected Publish command"),
3376 }
3377 }
3378
3379 #[test]
3381 fn test_parse_publish_defaults() {
3382 let args = vec!["apr", "publish", "./models", "org/repo"];
3383 let cli = parse_cli(args).expect("Failed to parse");
3384 match *cli.command {
3385 Commands::Publish {
3386 license,
3387 pipeline_tag,
3388 dry_run,
3389 model_name,
3390 library_name,
3391 tags,
3392 message,
3393 ..
3394 } => {
3395 assert_eq!(license, "mit");
3396 assert_eq!(pipeline_tag, "text-generation");
3397 assert!(!dry_run);
3398 assert!(model_name.is_none());
3399 assert!(library_name.is_none());
3400 assert!(tags.is_none());
3401 assert!(message.is_none());
3402 }
3403 _ => panic!("Expected Publish command"),
3404 }
3405 }
3406
3407 #[test]
3409 fn test_parse_eval_command() {
3410 let args = vec![
3411 "apr",
3412 "eval",
3413 "model.gguf",
3414 "--dataset",
3415 "lambada",
3416 "--text",
3417 "The quick brown fox",
3418 "--max-tokens",
3419 "256",
3420 "--threshold",
3421 "15.5",
3422 ];
3423 let cli = parse_cli(args).expect("Failed to parse");
3424 match *cli.command {
3425 Commands::Eval {
3426 file,
3427 dataset,
3428 text,
3429 max_tokens,
3430 threshold,
3431 } => {
3432 assert_eq!(file, PathBuf::from("model.gguf"));
3433 assert_eq!(dataset, "lambada");
3434 assert_eq!(text, Some("The quick brown fox".to_string()));
3435 assert_eq!(max_tokens, 256);
3436 assert!((threshold - 15.5).abs() < f32::EPSILON);
3437 }
3438 _ => panic!("Expected Eval command"),
3439 }
3440 }
3441
3442 #[test]
3444 fn test_parse_eval_defaults() {
3445 let args = vec!["apr", "eval", "model.apr"];
3446 let cli = parse_cli(args).expect("Failed to parse");
3447 match *cli.command {
3448 Commands::Eval {
3449 dataset,
3450 text,
3451 max_tokens,
3452 threshold,
3453 ..
3454 } => {
3455 assert_eq!(dataset, "wikitext-2");
3456 assert!(text.is_none());
3457 assert_eq!(max_tokens, 512);
3458 assert!((threshold - 20.0).abs() < f32::EPSILON);
3459 }
3460 _ => panic!("Expected Eval command"),
3461 }
3462 }
3463
3464 #[test]
3466 fn test_parse_flow_command() {
3467 let args = vec![
3468 "apr",
3469 "flow",
3470 "model.apr",
3471 "--layer",
3472 "encoder.0",
3473 "--component",
3474 "encoder",
3475 "-v",
3476 ];
3477 let cli = parse_cli(args).expect("Failed to parse");
3478 match *cli.command {
3479 Commands::Flow {
3480 file,
3481 layer,
3482 component,
3483 verbose,
3484 } => {
3485 assert_eq!(file, PathBuf::from("model.apr"));
3486 assert_eq!(layer, Some("encoder.0".to_string()));
3487 assert_eq!(component, "encoder");
3488 assert!(verbose);
3489 }
3490 _ => panic!("Expected Flow command"),
3491 }
3492 }
3493
3494 #[test]
3496 fn test_parse_flow_defaults() {
3497 let args = vec!["apr", "flow", "model.apr"];
3498 let cli = parse_cli(args).expect("Failed to parse");
3499 match *cli.command {
3500 Commands::Flow {
3501 component,
3502 verbose,
3503 layer,
3504 ..
3505 } => {
3506 assert_eq!(component, "full");
3507 assert!(!verbose);
3508 assert!(layer.is_none());
3509 }
3510 _ => panic!("Expected Flow command"),
3511 }
3512 }
3513
3514 #[test]
3516 fn test_parse_hex_command() {
3517 let args = vec![
3518 "apr",
3519 "hex",
3520 "model.apr",
3521 "--tensor",
3522 "embed.weight",
3523 "--limit",
3524 "128",
3525 "--stats",
3526 "--list",
3527 "--json",
3528 ];
3529 let cli = parse_cli(args).expect("Failed to parse");
3530 match *cli.command {
3531 Commands::Hex {
3532 file,
3533 tensor,
3534 limit,
3535 stats,
3536 list,
3537 json,
3538 ..
3539 } => {
3540 assert_eq!(file, PathBuf::from("model.apr"));
3541 assert_eq!(tensor, Some("embed.weight".to_string()));
3542 assert_eq!(limit, 128);
3543 assert!(stats);
3544 assert!(list);
3545 assert!(json);
3546 }
3547 _ => panic!("Expected Hex command"),
3548 }
3549 }
3550
3551 #[test]
3553 fn test_parse_hex_defaults() {
3554 let args = vec!["apr", "hex", "model.apr"];
3555 let cli = parse_cli(args).expect("Failed to parse");
3556 match *cli.command {
3557 Commands::Hex {
3558 limit,
3559 stats,
3560 list,
3561 json,
3562 tensor,
3563 ..
3564 } => {
3565 assert_eq!(limit, 64);
3566 assert!(!stats);
3567 assert!(!list);
3568 assert!(!json);
3569 assert!(tensor.is_none());
3570 }
3571 _ => panic!("Expected Hex command"),
3572 }
3573 }
3574
3575 #[test]
3577 fn test_parse_tree_command() {
3578 let args = vec![
3579 "apr",
3580 "tree",
3581 "model.apr",
3582 "--filter",
3583 "encoder",
3584 "--format",
3585 "mermaid",
3586 "--sizes",
3587 "--depth",
3588 "3",
3589 ];
3590 let cli = parse_cli(args).expect("Failed to parse");
3591 match *cli.command {
3592 Commands::Tree {
3593 file,
3594 filter,
3595 format,
3596 sizes,
3597 depth,
3598 } => {
3599 assert_eq!(file, PathBuf::from("model.apr"));
3600 assert_eq!(filter, Some("encoder".to_string()));
3601 assert_eq!(format, "mermaid");
3602 assert!(sizes);
3603 assert_eq!(depth, Some(3));
3604 }
3605 _ => panic!("Expected Tree command"),
3606 }
3607 }
3608
3609 #[test]
3611 fn test_parse_tree_defaults() {
3612 let args = vec!["apr", "tree", "model.apr"];
3613 let cli = parse_cli(args).expect("Failed to parse");
3614 match *cli.command {
3615 Commands::Tree {
3616 format,
3617 sizes,
3618 depth,
3619 filter,
3620 ..
3621 } => {
3622 assert_eq!(format, "ascii");
3623 assert!(!sizes);
3624 assert!(depth.is_none());
3625 assert!(filter.is_none());
3626 }
3627 _ => panic!("Expected Tree command"),
3628 }
3629 }
3630
3631 #[test]
3633 fn test_parse_probar_command() {
3634 let args = vec![
3635 "apr",
3636 "probar",
3637 "model.apr",
3638 "--output",
3639 "/tmp/probar",
3640 "--format",
3641 "json",
3642 "--golden",
3643 "/refs/golden",
3644 "--layer",
3645 "layer.0",
3646 ];
3647 let cli = parse_cli(args).expect("Failed to parse");
3648 match *cli.command {
3649 Commands::Probar {
3650 file,
3651 output,
3652 format,
3653 golden,
3654 layer,
3655 } => {
3656 assert_eq!(file, PathBuf::from("model.apr"));
3657 assert_eq!(output, PathBuf::from("/tmp/probar"));
3658 assert_eq!(format, "json");
3659 assert_eq!(golden, Some(PathBuf::from("/refs/golden")));
3660 assert_eq!(layer, Some("layer.0".to_string()));
3661 }
3662 _ => panic!("Expected Probar command"),
3663 }
3664 }
3665
3666 #[test]
3668 fn test_parse_probar_defaults() {
3669 let args = vec!["apr", "probar", "model.apr"];
3670 let cli = parse_cli(args).expect("Failed to parse");
3671 match *cli.command {
3672 Commands::Probar {
3673 output,
3674 format,
3675 golden,
3676 layer,
3677 ..
3678 } => {
3679 assert_eq!(output, PathBuf::from("./probar-export"));
3680 assert_eq!(format, "both");
3681 assert!(golden.is_none());
3682 assert!(layer.is_none());
3683 }
3684 _ => panic!("Expected Probar command"),
3685 }
3686 }
3687
3688 #[test]
3690 fn test_parse_debug_command() {
3691 let args = vec![
3692 "apr",
3693 "debug",
3694 "model.apr",
3695 "--drama",
3696 "--hex",
3697 "--strings",
3698 "--limit",
3699 "512",
3700 ];
3701 let cli = parse_cli(args).expect("Failed to parse");
3702 match *cli.command {
3703 Commands::Debug {
3704 file,
3705 drama,
3706 hex,
3707 strings,
3708 limit,
3709 } => {
3710 assert_eq!(file, PathBuf::from("model.apr"));
3711 assert!(drama);
3712 assert!(hex);
3713 assert!(strings);
3714 assert_eq!(limit, 512);
3715 }
3716 _ => panic!("Expected Debug command"),
3717 }
3718 }
3719
3720 #[test]
3722 fn test_parse_debug_defaults() {
3723 let args = vec!["apr", "debug", "model.apr"];
3724 let cli = parse_cli(args).expect("Failed to parse");
3725 match *cli.command {
3726 Commands::Debug {
3727 drama,
3728 hex,
3729 strings,
3730 limit,
3731 ..
3732 } => {
3733 assert!(!drama);
3734 assert!(!hex);
3735 assert!(!strings);
3736 assert_eq!(limit, 256);
3737 }
3738 _ => panic!("Expected Debug command"),
3739 }
3740 }
3741
3742 #[test]
3744 fn test_parse_tui_command_with_file() {
3745 let args = vec!["apr", "tui", "model.apr"];
3746 let cli = parse_cli(args).expect("Failed to parse");
3747 match *cli.command {
3748 Commands::Tui { file } => {
3749 assert_eq!(file, Some(PathBuf::from("model.apr")));
3750 }
3751 _ => panic!("Expected Tui command"),
3752 }
3753 }
3754
3755 #[test]
3757 fn test_parse_tui_command_no_file() {
3758 let args = vec!["apr", "tui"];
3759 let cli = parse_cli(args).expect("Failed to parse");
3760 match *cli.command {
3761 Commands::Tui { file } => {
3762 assert!(file.is_none());
3763 }
3764 _ => panic!("Expected Tui command"),
3765 }
3766 }
3767
3768 #[test]
3770 fn test_parse_import_command() {
3771 let args = vec![
3772 "apr",
3773 "import",
3774 "hf://openai/whisper-tiny",
3775 "--output",
3776 "whisper.apr",
3777 "--arch",
3778 "whisper",
3779 "--quantize",
3780 "int8",
3781 "--strict",
3782 "--preserve-q4k",
3783 "--tokenizer",
3784 "/path/to/tokenizer.json",
3785 ];
3786 let cli = parse_cli(args).expect("Failed to parse");
3787 match *cli.command {
3788 Commands::Import {
3789 source,
3790 output,
3791 arch,
3792 quantize,
3793 strict,
3794 preserve_q4k,
3795 tokenizer,
3796 enforce_provenance,
3797 } => {
3798 assert_eq!(source, "hf://openai/whisper-tiny");
3799 assert_eq!(output, Some(PathBuf::from("whisper.apr")));
3800 assert_eq!(arch, "whisper");
3801 assert_eq!(quantize, Some("int8".to_string()));
3802 assert!(strict);
3803 assert!(preserve_q4k);
3804 assert_eq!(tokenizer, Some(PathBuf::from("/path/to/tokenizer.json")));
3805 assert!(!enforce_provenance);
3806 }
3807 _ => panic!("Expected Import command"),
3808 }
3809 }
3810
3811 #[test]
3813 fn test_parse_import_defaults() {
3814 let args = vec!["apr", "import", "model.gguf"];
3815 let cli = parse_cli(args).expect("Failed to parse");
3816 match *cli.command {
3817 Commands::Import {
3818 arch,
3819 quantize,
3820 strict,
3821 preserve_q4k,
3822 output,
3823 tokenizer,
3824 ..
3825 } => {
3826 assert_eq!(arch, "auto");
3827 assert!(quantize.is_none());
3828 assert!(!strict);
3829 assert!(!preserve_q4k);
3830 assert!(output.is_none());
3831 assert!(tokenizer.is_none());
3832 }
3833 _ => panic!("Expected Import command"),
3834 }
3835 }
3836
3837 #[test]
3839 fn test_parse_export_command() {
3840 let args = vec![
3841 "apr",
3842 "export",
3843 "model.apr",
3844 "--format",
3845 "gguf",
3846 "-o",
3847 "model.gguf",
3848 "--quantize",
3849 "int4",
3850 ];
3851 let cli = parse_cli(args).expect("Failed to parse");
3852 match *cli.command {
3853 Commands::Export {
3854 file,
3855 format,
3856 output,
3857 quantize,
3858 } => {
3859 assert_eq!(file, PathBuf::from("model.apr"));
3860 assert_eq!(format, "gguf");
3861 assert_eq!(output, PathBuf::from("model.gguf"));
3862 assert_eq!(quantize, Some("int4".to_string()));
3863 }
3864 _ => panic!("Expected Export command"),
3865 }
3866 }
3867
3868 #[test]
3870 fn test_parse_export_defaults() {
3871 let args = vec!["apr", "export", "model.apr", "-o", "out.safetensors"];
3872 let cli = parse_cli(args).expect("Failed to parse");
3873 match *cli.command {
3874 Commands::Export {
3875 format, quantize, ..
3876 } => {
3877 assert_eq!(format, "safetensors");
3878 assert!(quantize.is_none());
3879 }
3880 _ => panic!("Expected Export command"),
3881 }
3882 }
3883
3884 #[test]
3886 fn test_parse_convert_command() {
3887 let args = vec![
3888 "apr",
3889 "convert",
3890 "model.apr",
3891 "--quantize",
3892 "q4k",
3893 "--compress",
3894 "zstd",
3895 "-o",
3896 "model-q4k.apr",
3897 "--force",
3898 ];
3899 let cli = parse_cli(args).expect("Failed to parse");
3900 match *cli.command {
3901 Commands::Convert {
3902 file,
3903 quantize,
3904 compress,
3905 output,
3906 force,
3907 } => {
3908 assert_eq!(file, PathBuf::from("model.apr"));
3909 assert_eq!(quantize, Some("q4k".to_string()));
3910 assert_eq!(compress, Some("zstd".to_string()));
3911 assert_eq!(output, PathBuf::from("model-q4k.apr"));
3912 assert!(force);
3913 }
3914 _ => panic!("Expected Convert command"),
3915 }
3916 }
3917
3918 #[test]
3920 fn test_parse_convert_defaults() {
3921 let args = vec!["apr", "convert", "model.apr", "-o", "out.apr"];
3922 let cli = parse_cli(args).expect("Failed to parse");
3923 match *cli.command {
3924 Commands::Convert {
3925 quantize,
3926 compress,
3927 force,
3928 ..
3929 } => {
3930 assert!(quantize.is_none());
3931 assert!(compress.is_none());
3932 assert!(!force);
3933 }
3934 _ => panic!("Expected Convert command"),
3935 }
3936 }
3937
3938 #[test]
3940 fn test_parse_oracle_command_with_source() {
3941 let args = vec![
3942 "apr",
3943 "oracle",
3944 "model.gguf",
3945 "--compliance",
3946 "--tensors",
3947 "--stats",
3948 "--explain",
3949 "--kernels",
3950 "--validate",
3951 "--full",
3952 ];
3953 let cli = parse_cli(args).expect("Failed to parse");
3954 match *cli.command {
3955 Commands::Oracle {
3956 source,
3957 compliance,
3958 tensors,
3959 stats,
3960 explain,
3961 kernels,
3962 validate,
3963 full,
3964 family,
3965 size,
3966 } => {
3967 assert_eq!(source, Some("model.gguf".to_string()));
3968 assert!(compliance);
3969 assert!(tensors);
3970 assert!(stats);
3971 assert!(explain);
3972 assert!(kernels);
3973 assert!(validate);
3974 assert!(full);
3975 assert!(family.is_none());
3976 assert!(size.is_none());
3977 }
3978 _ => panic!("Expected Oracle command"),
3979 }
3980 }
3981
3982 #[test]
3984 fn test_parse_oracle_family_mode() {
3985 let args = vec!["apr", "oracle", "--family", "qwen2", "--size", "7b"];
3986 let cli = parse_cli(args).expect("Failed to parse");
3987 match *cli.command {
3988 Commands::Oracle {
3989 source,
3990 family,
3991 size,
3992 ..
3993 } => {
3994 assert!(source.is_none());
3995 assert_eq!(family, Some("qwen2".to_string()));
3996 assert_eq!(size, Some("7b".to_string()));
3997 }
3998 _ => panic!("Expected Oracle command"),
3999 }
4000 }
4001
4002 #[test]
4004 fn test_parse_oracle_hf_uri() {
4005 let args = vec!["apr", "oracle", "hf://Qwen/Qwen2.5-Coder-1.5B"];
4006 let cli = parse_cli(args).expect("Failed to parse");
4007 match *cli.command {
4008 Commands::Oracle { source, .. } => {
4009 assert_eq!(source, Some("hf://Qwen/Qwen2.5-Coder-1.5B".to_string()));
4010 }
4011 _ => panic!("Expected Oracle command"),
4012 }
4013 }
4014
4015 #[test]
4017 fn test_parse_canary_create() {
4018 let args = vec![
4019 "apr",
4020 "canary",
4021 "create",
4022 "model.apr",
4023 "--input",
4024 "audio.wav",
4025 "--output",
4026 "canary.json",
4027 ];
4028 let cli = parse_cli(args).expect("Failed to parse");
4029 match *cli.command {
4030 Commands::Canary { command } => match command {
4031 CanaryCommands::Create {
4032 file,
4033 input,
4034 output,
4035 } => {
4036 assert_eq!(file, PathBuf::from("model.apr"));
4037 assert_eq!(input, PathBuf::from("audio.wav"));
4038 assert_eq!(output, PathBuf::from("canary.json"));
4039 }
4040 _ => panic!("Expected Create subcommand"),
4041 },
4042 _ => panic!("Expected Canary command"),
4043 }
4044 }
4045
4046 #[test]
4048 fn test_parse_canary_check() {
4049 let args = vec![
4050 "apr",
4051 "canary",
4052 "check",
4053 "model.apr",
4054 "--canary",
4055 "canary.json",
4056 ];
4057 let cli = parse_cli(args).expect("Failed to parse");
4058 match *cli.command {
4059 Commands::Canary { command } => match command {
4060 CanaryCommands::Check { file, canary } => {
4061 assert_eq!(file, PathBuf::from("model.apr"));
4062 assert_eq!(canary, PathBuf::from("canary.json"));
4063 }
4064 _ => panic!("Expected Check subcommand"),
4065 },
4066 _ => panic!("Expected Canary command"),
4067 }
4068 }
4069
4070 #[test]
4072 fn test_parse_compare_hf_command() {
4073 let args = vec![
4074 "apr",
4075 "compare-hf",
4076 "model.apr",
4077 "--hf",
4078 "openai/whisper-tiny",
4079 "--tensor",
4080 "encoder.0",
4081 "--threshold",
4082 "1e-3",
4083 "--json",
4084 ];
4085 let cli = parse_cli(args).expect("Failed to parse");
4086 match *cli.command {
4087 Commands::CompareHf {
4088 file,
4089 hf,
4090 tensor,
4091 threshold,
4092 json,
4093 } => {
4094 assert_eq!(file, PathBuf::from("model.apr"));
4095 assert_eq!(hf, "openai/whisper-tiny");
4096 assert_eq!(tensor, Some("encoder.0".to_string()));
4097 assert!((threshold - 1e-3).abs() < f64::EPSILON);
4098 assert!(json);
4099 }
4100 _ => panic!("Expected CompareHf command"),
4101 }
4102 }
4103
4104 #[test]
4106 fn test_parse_compare_hf_defaults() {
4107 let args = vec![
4108 "apr",
4109 "compare-hf",
4110 "model.apr",
4111 "--hf",
4112 "openai/whisper-tiny",
4113 ];
4114 let cli = parse_cli(args).expect("Failed to parse");
4115 match *cli.command {
4116 Commands::CompareHf {
4117 tensor,
4118 threshold,
4119 json,
4120 ..
4121 } => {
4122 assert!(tensor.is_none());
4123 assert!((threshold - 1e-5).abs() < f64::EPSILON);
4124 assert!(!json);
4125 }
4126 _ => panic!("Expected CompareHf command"),
4127 }
4128 }
4129
4130 #[test]
4132 fn test_parse_pull_command() {
4133 let args = vec!["apr", "pull", "hf://Qwen/Qwen2.5-Coder-1.5B", "--force"];
4134 let cli = parse_cli(args).expect("Failed to parse");
4135 match *cli.command {
4136 Commands::Pull { model_ref, force } => {
4137 assert_eq!(model_ref, "hf://Qwen/Qwen2.5-Coder-1.5B");
4138 assert!(force);
4139 }
4140 _ => panic!("Expected Pull command"),
4141 }
4142 }
4143
4144 #[test]
4146 fn test_parse_pull_defaults() {
4147 let args = vec!["apr", "pull", "qwen2.5-coder"];
4148 let cli = parse_cli(args).expect("Failed to parse");
4149 match *cli.command {
4150 Commands::Pull { model_ref, force } => {
4151 assert_eq!(model_ref, "qwen2.5-coder");
4152 assert!(!force);
4153 }
4154 _ => panic!("Expected Pull command"),
4155 }
4156 }
4157
4158 #[test]
4160 fn test_parse_tune_command() {
4161 let args = vec![
4162 "apr",
4163 "tune",
4164 "model.apr",
4165 "--method",
4166 "lora",
4167 "--rank",
4168 "16",
4169 "--vram",
4170 "24.0",
4171 "--plan",
4172 "--model",
4173 "7B",
4174 "--freeze-base",
4175 "--train-data",
4176 "data.jsonl",
4177 "--json",
4178 ];
4179 let cli = parse_cli(args).expect("Failed to parse");
4180 match *cli.command {
4181 Commands::Tune {
4182 file,
4183 method,
4184 rank,
4185 vram,
4186 plan,
4187 model,
4188 freeze_base,
4189 train_data,
4190 json,
4191 } => {
4192 assert_eq!(file, Some(PathBuf::from("model.apr")));
4193 assert_eq!(method, "lora");
4194 assert_eq!(rank, Some(16));
4195 assert!((vram - 24.0).abs() < f64::EPSILON);
4196 assert!(plan);
4197 assert_eq!(model, Some("7B".to_string()));
4198 assert!(freeze_base);
4199 assert_eq!(train_data, Some(PathBuf::from("data.jsonl")));
4200 assert!(json);
4201 }
4202 _ => panic!("Expected Tune command"),
4203 }
4204 }
4205
4206 #[test]
4208 fn test_parse_tune_defaults() {
4209 let args = vec!["apr", "tune"];
4210 let cli = parse_cli(args).expect("Failed to parse");
4211 match *cli.command {
4212 Commands::Tune {
4213 file,
4214 method,
4215 rank,
4216 vram,
4217 plan,
4218 model,
4219 freeze_base,
4220 train_data,
4221 json,
4222 } => {
4223 assert!(file.is_none());
4224 assert_eq!(method, "auto");
4225 assert!(rank.is_none());
4226 assert!((vram - 16.0).abs() < f64::EPSILON);
4227 assert!(!plan);
4228 assert!(model.is_none());
4229 assert!(!freeze_base);
4230 assert!(train_data.is_none());
4231 assert!(!json);
4232 }
4233 _ => panic!("Expected Tune command"),
4234 }
4235 }
4236
4237 #[test]
4239 fn test_parse_check_command() {
4240 let args = vec!["apr", "check", "model.apr", "--no-gpu"];
4241 let cli = parse_cli(args).expect("Failed to parse");
4242 match *cli.command {
4243 Commands::Check { file, no_gpu } => {
4244 assert_eq!(file, PathBuf::from("model.apr"));
4245 assert!(no_gpu);
4246 }
4247 _ => panic!("Expected Check command"),
4248 }
4249 }
4250
4251 #[test]
4253 fn test_parse_check_defaults() {
4254 let args = vec!["apr", "check", "model.gguf"];
4255 let cli = parse_cli(args).expect("Failed to parse");
4256 match *cli.command {
4257 Commands::Check { no_gpu, .. } => {
4258 assert!(!no_gpu);
4259 }
4260 _ => panic!("Expected Check command"),
4261 }
4262 }
4263
4264 #[test]
4266 fn test_parse_lint_command() {
4267 let args = vec!["apr", "lint", "model.apr"];
4268 let cli = parse_cli(args).expect("Failed to parse");
4269 match *cli.command {
4270 Commands::Lint { file } => {
4271 assert_eq!(file, PathBuf::from("model.apr"));
4272 }
4273 _ => panic!("Expected Lint command"),
4274 }
4275 }
4276
4277 #[test]
4279 fn test_parse_tensors_command() {
4280 let args = vec![
4281 "apr",
4282 "tensors",
4283 "model.apr",
4284 "--stats",
4285 "--filter",
4286 "encoder",
4287 "--limit",
4288 "20",
4289 "--json",
4290 ];
4291 let cli = parse_cli(args).expect("Failed to parse");
4292 match *cli.command {
4293 Commands::Tensors {
4294 file,
4295 stats,
4296 filter,
4297 limit,
4298 json,
4299 } => {
4300 assert_eq!(file, PathBuf::from("model.apr"));
4301 assert!(stats);
4302 assert_eq!(filter, Some("encoder".to_string()));
4303 assert_eq!(limit, 20);
4304 assert!(json);
4305 }
4306 _ => panic!("Expected Tensors command"),
4307 }
4308 }
4309
4310 #[test]
4312 fn test_parse_explain_with_code() {
4313 let args = vec!["apr", "explain", "E001"];
4314 let cli = parse_cli(args).expect("Failed to parse");
4315 match *cli.command {
4316 Commands::Explain {
4317 code, file, tensor, ..
4318 } => {
4319 assert_eq!(code, Some("E001".to_string()));
4320 assert!(file.is_none());
4321 assert!(tensor.is_none());
4322 }
4323 _ => panic!("Expected Explain command"),
4324 }
4325 }
4326
4327 #[test]
4329 fn test_parse_explain_with_tensor_and_file() {
4330 let args = vec![
4331 "apr",
4332 "explain",
4333 "--file",
4334 "model.apr",
4335 "--tensor",
4336 "embed.weight",
4337 ];
4338 let cli = parse_cli(args).expect("Failed to parse");
4339 match *cli.command {
4340 Commands::Explain { code, file, tensor } => {
4341 assert!(code.is_none());
4342 assert_eq!(file, Some(PathBuf::from("model.apr")));
4343 assert_eq!(tensor, Some("embed.weight".to_string()));
4344 }
4345 _ => panic!("Expected Explain command"),
4346 }
4347 }
4348
4349 #[test]
4351 fn test_parse_trace_command() {
4352 let args = vec![
4353 "apr",
4354 "trace",
4355 "model.apr",
4356 "--layer",
4357 "layer.0",
4358 "--reference",
4359 "ref.apr",
4360 "--json",
4361 "-v",
4362 "--payload",
4363 "--diff",
4364 "--interactive",
4365 ];
4366 let cli = parse_cli(args).expect("Failed to parse");
4367 match *cli.command {
4368 Commands::Trace {
4369 file,
4370 layer,
4371 reference,
4372 json,
4373 verbose,
4374 payload,
4375 diff,
4376 interactive,
4377 } => {
4378 assert_eq!(file, PathBuf::from("model.apr"));
4379 assert_eq!(layer, Some("layer.0".to_string()));
4380 assert_eq!(reference, Some(PathBuf::from("ref.apr")));
4381 assert!(json);
4382 assert!(verbose);
4383 assert!(payload);
4384 assert!(diff);
4385 assert!(interactive);
4386 }
4387 _ => panic!("Expected Trace command"),
4388 }
4389 }
4390
4391 #[test]
4393 fn test_parse_validate_with_min_score() {
4394 let args = vec!["apr", "validate", "model.apr", "--min-score", "80"];
4395 let cli = parse_cli(args).expect("Failed to parse");
4396 match *cli.command {
4397 Commands::Validate {
4398 min_score, strict, ..
4399 } => {
4400 assert_eq!(min_score, Some(80));
4401 assert!(!strict);
4402 }
4403 _ => panic!("Expected Validate command"),
4404 }
4405 }
4406
4407 #[test]
4409 fn test_parse_diff_with_all_options() {
4410 let args = vec![
4411 "apr",
4412 "diff",
4413 "a.apr",
4414 "b.apr",
4415 "--values",
4416 "--filter",
4417 "embed",
4418 "--limit",
4419 "5",
4420 "--transpose-aware",
4421 "--json",
4422 ];
4423 let cli = parse_cli(args).expect("Failed to parse");
4424 match *cli.command {
4425 Commands::Diff {
4426 file1,
4427 file2,
4428 values,
4429 filter,
4430 limit,
4431 transpose_aware,
4432 json,
4433 ..
4434 } => {
4435 assert_eq!(file1, PathBuf::from("a.apr"));
4436 assert_eq!(file2, PathBuf::from("b.apr"));
4437 assert!(values);
4438 assert_eq!(filter, Some("embed".to_string()));
4439 assert_eq!(limit, 5);
4440 assert!(transpose_aware);
4441 assert!(json);
4442 }
4443 _ => panic!("Expected Diff command"),
4444 }
4445 }
4446
4447 #[test]
4449 fn test_parse_run_with_chat_flag() {
4450 let args = vec![
4451 "apr",
4452 "run",
4453 "model.gguf",
4454 "--prompt",
4455 "Hello world",
4456 "--chat",
4457 ];
4458 let cli = parse_cli(args).expect("Failed to parse");
4459 match *cli.command {
4460 Commands::Run {
4461 chat,
4462 prompt,
4463 source,
4464 ..
4465 } => {
4466 assert!(chat);
4467 assert_eq!(prompt, Some("Hello world".to_string()));
4468 assert_eq!(source, "model.gguf");
4469 }
4470 _ => panic!("Expected Run command"),
4471 }
4472 }
4473
4474 #[test]
4476 fn test_parse_run_with_trace_payload() {
4477 let args = vec![
4478 "apr",
4479 "run",
4480 "model.gguf",
4481 "--prompt",
4482 "test",
4483 "--trace-payload",
4484 ];
4485 let cli = parse_cli(args).expect("Failed to parse");
4486 match *cli.command {
4487 Commands::Run {
4488 trace_payload,
4489 trace,
4490 trace_level,
4491 ..
4492 } => {
4493 assert!(trace_payload);
4494 assert!(!trace);
4496 assert_eq!(trace_level, "basic");
4497 }
4498 _ => panic!("Expected Run command"),
4499 }
4500 }
4501
4502 #[test]
4504 fn test_parse_run_positional_prompt() {
4505 let args = vec!["apr", "run", "model.gguf", "What is 2+2?"];
4506 let cli = parse_cli(args).expect("Failed to parse");
4507 match *cli.command {
4508 Commands::Run {
4509 source,
4510 positional_prompt,
4511 prompt,
4512 ..
4513 } => {
4514 assert_eq!(source, "model.gguf");
4515 assert_eq!(positional_prompt, Some("What is 2+2?".to_string()));
4516 assert_eq!(prompt, None);
4517 }
4518 _ => panic!("Expected Run command"),
4519 }
4520 }
4521
4522 #[test]
4524 fn test_parse_run_flag_prompt_overrides_positional() {
4525 let args = vec![
4526 "apr",
4527 "run",
4528 "model.gguf",
4529 "positional text",
4530 "--prompt",
4531 "flag text",
4532 ];
4533 let cli = parse_cli(args).expect("Failed to parse");
4534 match *cli.command {
4535 Commands::Run {
4536 positional_prompt,
4537 prompt,
4538 ..
4539 } => {
4540 assert_eq!(positional_prompt, Some("positional text".to_string()));
4541 assert_eq!(prompt, Some("flag text".to_string()));
4542 }
4543 _ => panic!("Expected Run command"),
4544 }
4545 }
4546
4547 #[test]
4549 fn test_parse_run_positional_prompt_with_n_flag() {
4550 let args = vec!["apr", "run", "model.gguf", "What is 2+2?", "-n", "64"];
4551 let cli = parse_cli(args).expect("Failed to parse");
4552 match *cli.command {
4553 Commands::Run {
4554 source,
4555 positional_prompt,
4556 max_tokens,
4557 ..
4558 } => {
4559 assert_eq!(source, "model.gguf");
4560 assert_eq!(positional_prompt, Some("What is 2+2?".to_string()));
4561 assert_eq!(max_tokens, 64);
4562 }
4563 _ => panic!("Expected Run command"),
4564 }
4565 }
4566
4567 #[test]
4569 fn test_parse_run_no_prompt() {
4570 let args = vec!["apr", "run", "model.gguf"];
4571 let cli = parse_cli(args).expect("Failed to parse");
4572 match *cli.command {
4573 Commands::Run {
4574 positional_prompt,
4575 prompt,
4576 ..
4577 } => {
4578 assert_eq!(positional_prompt, None);
4579 assert_eq!(prompt, None);
4580 }
4581 _ => panic!("Expected Run command"),
4582 }
4583 }
4584
4585 #[test]
4587 fn test_parse_run_with_local_verbose() {
4588 let args = vec!["apr", "run", "model.gguf", "--prompt", "hi", "-v"];
4589 let cli = parse_cli(args).expect("Failed to parse");
4590 match *cli.command {
4591 Commands::Run { verbose, .. } => {
4592 assert!(verbose);
4593 }
4594 _ => panic!("Expected Run command"),
4595 }
4596 }
4597
4598 #[test]
4600 fn test_parse_run_with_full_trace() {
4601 let args = vec![
4602 "apr",
4603 "run",
4604 "model.gguf",
4605 "--prompt",
4606 "test",
4607 "--trace",
4608 "--trace-steps",
4609 "Tokenize,Embed,Attention",
4610 "--trace-verbose",
4611 "--trace-output",
4612 "/tmp/trace.json",
4613 "--trace-level",
4614 "layer",
4615 ];
4616 let cli = parse_cli(args).expect("Failed to parse");
4617 match *cli.command {
4618 Commands::Run {
4619 trace,
4620 trace_steps,
4621 trace_verbose,
4622 trace_output,
4623 trace_level,
4624 ..
4625 } => {
4626 assert!(trace);
4627 assert_eq!(
4628 trace_steps,
4629 Some(vec![
4630 "Tokenize".to_string(),
4631 "Embed".to_string(),
4632 "Attention".to_string()
4633 ])
4634 );
4635 assert!(trace_verbose);
4636 assert_eq!(trace_output, Some(PathBuf::from("/tmp/trace.json")));
4637 assert_eq!(trace_level, "layer");
4638 }
4639 _ => panic!("Expected Run command"),
4640 }
4641 }
4642
4643 #[test]
4645 fn test_parse_run_benchmark_and_profile() {
4646 let args = vec![
4647 "apr",
4648 "run",
4649 "model.gguf",
4650 "--prompt",
4651 "test",
4652 "--benchmark",
4653 "--profile",
4654 ];
4655 let cli = parse_cli(args).expect("Failed to parse");
4656 match *cli.command {
4657 Commands::Run {
4658 benchmark, profile, ..
4659 } => {
4660 assert!(benchmark);
4661 assert!(profile);
4662 }
4663 _ => panic!("Expected Run command"),
4664 }
4665 }
4666
4667 #[test]
4669 fn test_parse_run_no_gpu() {
4670 let args = vec!["apr", "run", "model.gguf", "--prompt", "test", "--no-gpu"];
4671 let cli = parse_cli(args).expect("Failed to parse");
4672 match *cli.command {
4673 Commands::Run { no_gpu, .. } => {
4674 assert!(no_gpu);
4675 }
4676 _ => panic!("Expected Run command"),
4677 }
4678 }
4679
4680 #[test]
4682 fn test_parse_run_offline() {
4683 let args = vec!["apr", "run", "model.gguf", "--prompt", "test", "--offline"];
4684 let cli = parse_cli(args).expect("Failed to parse");
4685 match *cli.command {
4686 Commands::Run { offline, .. } => {
4687 assert!(offline);
4688 }
4689 _ => panic!("Expected Run command"),
4690 }
4691 }
4692
4693 #[test]
4695 fn test_parse_run_stream_and_format() {
4696 let args = vec![
4697 "apr",
4698 "run",
4699 "model.gguf",
4700 "--prompt",
4701 "test",
4702 "--stream",
4703 "-f",
4704 "json",
4705 ];
4706 let cli = parse_cli(args).expect("Failed to parse");
4707 match *cli.command {
4708 Commands::Run { stream, format, .. } => {
4709 assert!(stream);
4710 assert_eq!(format, "json");
4711 }
4712 _ => panic!("Expected Run command"),
4713 }
4714 }
4715
4716 #[test]
4718 fn test_parse_run_asr_options() {
4719 let args = vec![
4720 "apr",
4721 "run",
4722 "hf://openai/whisper-tiny",
4723 "--input",
4724 "audio.wav",
4725 "--language",
4726 "en",
4727 "--task",
4728 "transcribe",
4729 ];
4730 let cli = parse_cli(args).expect("Failed to parse");
4731 match *cli.command {
4732 Commands::Run {
4733 input,
4734 language,
4735 task,
4736 ..
4737 } => {
4738 assert_eq!(input, Some(PathBuf::from("audio.wav")));
4739 assert_eq!(language, Some("en".to_string()));
4740 assert_eq!(task, Some("transcribe".to_string()));
4741 }
4742 _ => panic!("Expected Run command"),
4743 }
4744 }
4745
4746 #[test]
4748 fn test_parse_remove_alias() {
4749 let args = vec!["apr", "remove", "my-model"];
4750 let cli = parse_cli(args).expect("Failed to parse");
4751 match *cli.command {
4752 Commands::Rm { model_ref } => {
4753 assert_eq!(model_ref, "my-model");
4754 }
4755 _ => panic!("Expected Rm command"),
4756 }
4757 }
4758
4759 #[test]
4761 fn test_parse_qa_all_skip_flags() {
4762 let args = vec![
4763 "apr",
4764 "qa",
4765 "model.gguf",
4766 "--skip-golden",
4767 "--skip-throughput",
4768 "--skip-ollama",
4769 "--skip-gpu-speedup",
4770 "--skip-contract",
4771 "--skip-format-parity",
4772 "--safetensors-path",
4773 "model.safetensors",
4774 "--iterations",
4775 "20",
4776 "--warmup",
4777 "5",
4778 "--max-tokens",
4779 "64",
4780 "--json",
4781 "-v",
4782 ];
4783 let cli = parse_cli(args).expect("Failed to parse");
4784 match *cli.command {
4785 Commands::Qa {
4786 skip_golden,
4787 skip_throughput,
4788 skip_ollama,
4789 skip_gpu_speedup,
4790 skip_contract,
4791 skip_format_parity,
4792 safetensors_path,
4793 iterations,
4794 warmup,
4795 max_tokens,
4796 json,
4797 verbose,
4798 ..
4799 } => {
4800 assert!(skip_golden);
4801 assert!(skip_throughput);
4802 assert!(skip_ollama);
4803 assert!(skip_gpu_speedup);
4804 assert!(skip_contract);
4805 assert!(skip_format_parity);
4806 assert_eq!(safetensors_path, Some(PathBuf::from("model.safetensors")));
4807 assert_eq!(iterations, 20);
4808 assert_eq!(warmup, 5);
4809 assert_eq!(max_tokens, 64);
4810 assert!(json);
4811 assert!(verbose);
4812 }
4813 _ => panic!("Expected Qa command"),
4814 }
4815 }
4816
4817 #[test]
4819 fn test_parse_serve_all_options() {
4820 let args = vec![
4821 "apr",
4822 "serve",
4823 "model.apr",
4824 "--port",
4825 "9090",
4826 "--host",
4827 "0.0.0.0",
4828 "--no-cors",
4829 "--no-metrics",
4830 "--no-gpu",
4831 "--batch",
4832 "--trace",
4833 "--trace-level",
4834 "layer",
4835 "--profile",
4836 ];
4837 let cli = parse_cli(args).expect("Failed to parse");
4838 match *cli.command {
4839 Commands::Serve {
4840 port,
4841 host,
4842 no_cors,
4843 no_metrics,
4844 no_gpu,
4845 batch,
4846 trace,
4847 trace_level,
4848 profile,
4849 ..
4850 } => {
4851 assert_eq!(port, 9090);
4852 assert_eq!(host, "0.0.0.0");
4853 assert!(no_cors);
4854 assert!(no_metrics);
4855 assert!(no_gpu);
4856 assert!(batch);
4857 assert!(trace);
4858 assert_eq!(trace_level, "layer");
4859 assert!(profile);
4860 }
4861 _ => panic!("Expected Serve command"),
4862 }
4863 }
4864
4865 #[test]
4867 fn test_parse_bench_all_options() {
4868 let args = vec![
4869 "apr",
4870 "bench",
4871 "model.gguf",
4872 "--warmup",
4873 "10",
4874 "--iterations",
4875 "20",
4876 "--max-tokens",
4877 "64",
4878 "--prompt",
4879 "The quick brown fox",
4880 "--fast",
4881 "--brick",
4882 "attention",
4883 ];
4884 let cli = parse_cli(args).expect("Failed to parse");
4885 match *cli.command {
4886 Commands::Bench {
4887 warmup,
4888 iterations,
4889 max_tokens,
4890 prompt,
4891 fast,
4892 brick,
4893 ..
4894 } => {
4895 assert_eq!(warmup, 10);
4896 assert_eq!(iterations, 20);
4897 assert_eq!(max_tokens, 64);
4898 assert_eq!(prompt, Some("The quick brown fox".to_string()));
4899 assert!(fast);
4900 assert_eq!(brick, Some("attention".to_string()));
4901 }
4902 _ => panic!("Expected Bench command"),
4903 }
4904 }
4905
4906 #[test]
4908 fn test_parse_cbtop_speculative() {
4909 let args = vec![
4910 "apr",
4911 "cbtop",
4912 "--model-path",
4913 "model.gguf",
4914 "--speculative",
4915 "--speculation-k",
4916 "8",
4917 "--draft-model",
4918 "draft.gguf",
4919 "--concurrent",
4920 "4",
4921 "--simulated",
4922 ];
4923 let cli = parse_cli(args).expect("Failed to parse");
4924 match *cli.command {
4925 Commands::Cbtop {
4926 model_path,
4927 speculative,
4928 speculation_k,
4929 draft_model,
4930 concurrent,
4931 simulated,
4932 ..
4933 } => {
4934 assert_eq!(model_path, Some(PathBuf::from("model.gguf")));
4935 assert!(speculative);
4936 assert_eq!(speculation_k, 8);
4937 assert_eq!(draft_model, Some(PathBuf::from("draft.gguf")));
4938 assert_eq!(concurrent, 4);
4939 assert!(simulated);
4940 }
4941 _ => panic!("Expected Cbtop command"),
4942 }
4943 }
4944
4945 #[test]
4947 fn test_parse_profile_energy_perf() {
4948 let args = vec![
4949 "apr",
4950 "profile",
4951 "model.apr",
4952 "--energy",
4953 "--perf-grade",
4954 "--callgraph",
4955 "--compare-hf",
4956 "openai/whisper-tiny",
4957 "--output",
4958 "/tmp/flame.svg",
4959 ];
4960 let cli = parse_cli(args).expect("Failed to parse");
4961 match *cli.command {
4962 Commands::Profile {
4963 energy,
4964 perf_grade,
4965 callgraph,
4966 compare_hf,
4967 output,
4968 ..
4969 } => {
4970 assert!(energy);
4971 assert!(perf_grade);
4972 assert!(callgraph);
4973 assert_eq!(compare_hf, Some("openai/whisper-tiny".to_string()));
4974 assert_eq!(output, Some(PathBuf::from("/tmp/flame.svg")));
4975 }
4976 _ => panic!("Expected Profile command"),
4977 }
4978 }
4979
4980 #[test]
4982 fn test_parse_chat_with_trace() {
4983 let args = vec![
4984 "apr",
4985 "chat",
4986 "model.gguf",
4987 "--system",
4988 "You are a helpful assistant.",
4989 "--inspect",
4990 "--trace",
4991 "--trace-steps",
4992 "Tokenize,Decode",
4993 "--trace-verbose",
4994 "--trace-output",
4995 "/tmp/chat-trace.json",
4996 "--trace-level",
4997 "payload",
4998 "--profile",
4999 ];
5000 let cli = parse_cli(args).expect("Failed to parse");
5001 match *cli.command {
5002 Commands::Chat {
5003 system,
5004 inspect,
5005 trace,
5006 trace_steps,
5007 trace_verbose,
5008 trace_output,
5009 trace_level,
5010 profile,
5011 ..
5012 } => {
5013 assert_eq!(system, Some("You are a helpful assistant.".to_string()));
5014 assert!(inspect);
5015 assert!(trace);
5016 assert_eq!(
5017 trace_steps,
5018 Some(vec!["Tokenize".to_string(), "Decode".to_string()])
5019 );
5020 assert!(trace_verbose);
5021 assert_eq!(trace_output, Some(PathBuf::from("/tmp/chat-trace.json")));
5022 assert_eq!(trace_level, "payload");
5023 assert!(profile);
5024 }
5025 _ => panic!("Expected Chat command"),
5026 }
5027 }
5028
5029 #[test]
5031 fn test_parse_showcase_with_step() {
5032 let args = vec![
5033 "apr", "showcase", "--step", "bench", "--tier", "tiny", "--zram", "--runs", "50",
5034 "--json", "-v", "-q",
5035 ];
5036 let cli = parse_cli(args).expect("Failed to parse");
5037 match *cli.command {
5038 Commands::Showcase {
5039 step,
5040 tier,
5041 zram,
5042 runs,
5043 json,
5044 verbose,
5045 quiet,
5046 ..
5047 } => {
5048 assert_eq!(step, Some("bench".to_string()));
5049 assert_eq!(tier, "tiny");
5050 assert!(zram);
5051 assert_eq!(runs, 50);
5052 assert!(json);
5053 assert!(verbose);
5054 assert!(quiet);
5055 }
5056 _ => panic!("Expected Showcase command"),
5057 }
5058 }
5059
5060 #[test]
5062 fn test_parse_rosetta_compare_inference() {
5063 let args = vec![
5064 "apr",
5065 "rosetta",
5066 "compare-inference",
5067 "model_a.gguf",
5068 "model_b.apr",
5069 "--prompt",
5070 "What is 2+2?",
5071 "--max-tokens",
5072 "10",
5073 "--temperature",
5074 "0.5",
5075 "--tolerance",
5076 "0.05",
5077 "--json",
5078 ];
5079 let cli = parse_cli(args).expect("Failed to parse");
5080 match *cli.command {
5081 Commands::Rosetta { action } => match action {
5082 RosettaCommands::CompareInference {
5083 model_a,
5084 model_b,
5085 prompt,
5086 max_tokens,
5087 temperature,
5088 tolerance,
5089 json,
5090 } => {
5091 assert_eq!(model_a, PathBuf::from("model_a.gguf"));
5092 assert_eq!(model_b, PathBuf::from("model_b.apr"));
5093 assert_eq!(prompt, "What is 2+2?");
5094 assert_eq!(max_tokens, 10);
5095 assert!((temperature - 0.5).abs() < f32::EPSILON);
5096 assert!((tolerance - 0.05).abs() < f32::EPSILON);
5097 assert!(json);
5098 }
5099 _ => panic!("Expected CompareInference subcommand"),
5100 },
5101 _ => panic!("Expected Rosetta command"),
5102 }
5103 }
5104
5105 #[test]
5107 fn test_parse_rosetta_diff_tensors() {
5108 let args = vec![
5109 "apr",
5110 "rosetta",
5111 "diff-tensors",
5112 "ref.gguf",
5113 "test.apr",
5114 "--mismatches-only",
5115 "--show-values",
5116 "5",
5117 "--filter",
5118 "lm_head",
5119 "--json",
5120 ];
5121 let cli = parse_cli(args).expect("Failed to parse");
5122 match *cli.command {
5123 Commands::Rosetta { action } => match action {
5124 RosettaCommands::DiffTensors {
5125 model_a,
5126 model_b,
5127 mismatches_only,
5128 show_values,
5129 filter,
5130 json,
5131 } => {
5132 assert_eq!(model_a, PathBuf::from("ref.gguf"));
5133 assert_eq!(model_b, PathBuf::from("test.apr"));
5134 assert!(mismatches_only);
5135 assert_eq!(show_values, 5);
5136 assert_eq!(filter, Some("lm_head".to_string()));
5137 assert!(json);
5138 }
5139 _ => panic!("Expected DiffTensors subcommand"),
5140 },
5141 _ => panic!("Expected Rosetta command"),
5142 }
5143 }
5144
5145 #[test]
5147 fn test_parse_rosetta_fingerprint() {
5148 let args = vec![
5149 "apr",
5150 "rosetta",
5151 "fingerprint",
5152 "model.gguf",
5153 "model2.apr",
5154 "--output",
5155 "fingerprints.json",
5156 "--filter",
5157 "encoder",
5158 "--verbose",
5159 "--json",
5160 ];
5161 let cli = parse_cli(args).expect("Failed to parse");
5162 match *cli.command {
5163 Commands::Rosetta { action } => match action {
5164 RosettaCommands::Fingerprint {
5165 model,
5166 model_b,
5167 output,
5168 filter,
5169 verbose,
5170 json,
5171 } => {
5172 assert_eq!(model, PathBuf::from("model.gguf"));
5173 assert_eq!(model_b, Some(PathBuf::from("model2.apr")));
5174 assert_eq!(output, Some(PathBuf::from("fingerprints.json")));
5175 assert_eq!(filter, Some("encoder".to_string()));
5176 assert!(verbose);
5177 assert!(json);
5178 }
5179 _ => panic!("Expected Fingerprint subcommand"),
5180 },
5181 _ => panic!("Expected Rosetta command"),
5182 }
5183 }
5184
5185 #[test]
5187 fn test_parse_rosetta_validate_stats() {
5188 let args = vec![
5189 "apr",
5190 "rosetta",
5191 "validate-stats",
5192 "model.apr",
5193 "--reference",
5194 "ref.gguf",
5195 "--fingerprints",
5196 "fp.json",
5197 "--threshold",
5198 "5.0",
5199 "--strict",
5200 "--json",
5201 ];
5202 let cli = parse_cli(args).expect("Failed to parse");
5203 match *cli.command {
5204 Commands::Rosetta { action } => match action {
5205 RosettaCommands::ValidateStats {
5206 model,
5207 reference,
5208 fingerprints,
5209 threshold,
5210 strict,
5211 json,
5212 } => {
5213 assert_eq!(model, PathBuf::from("model.apr"));
5214 assert_eq!(reference, Some(PathBuf::from("ref.gguf")));
5215 assert_eq!(fingerprints, Some(PathBuf::from("fp.json")));
5216 assert!((threshold - 5.0).abs() < f32::EPSILON);
5217 assert!(strict);
5218 assert!(json);
5219 }
5220 _ => panic!("Expected ValidateStats subcommand"),
5221 },
5222 _ => panic!("Expected Rosetta command"),
5223 }
5224 }
5225
5226 #[test]
5232 fn test_global_offline_flag() {
5233 let args = vec!["apr", "--offline", "inspect", "model.apr"];
5234 let cli = parse_cli(args).expect("Failed to parse");
5235 assert!(cli.offline);
5236 }
5237
5238 #[test]
5240 fn test_global_quiet_flag() {
5241 let args = vec!["apr", "--quiet", "inspect", "model.apr"];
5242 let cli = parse_cli(args).expect("Failed to parse");
5243 assert!(cli.quiet);
5244 }
5245
5246 #[test]
5248 fn test_multiple_global_flags() {
5249 let args = vec![
5250 "apr",
5251 "--verbose",
5252 "--json",
5253 "--offline",
5254 "--quiet",
5255 "--skip-contract",
5256 "inspect",
5257 "model.apr",
5258 ];
5259 let cli = parse_cli(args).expect("Failed to parse");
5260 assert!(cli.verbose);
5261 assert!(cli.json);
5262 assert!(cli.offline);
5263 assert!(cli.quiet);
5264 assert!(cli.skip_contract);
5265 }
5266
5267 #[test]
5269 fn test_global_flags_default_false() {
5270 let args = vec!["apr", "inspect", "model.apr"];
5271 let cli = parse_cli(args).expect("Failed to parse");
5272 assert!(!cli.verbose);
5273 assert!(!cli.json);
5274 assert!(!cli.offline);
5275 assert!(!cli.quiet);
5276 assert!(!cli.skip_contract);
5277 }
5278
5279 #[test]
5285 fn test_extract_paths_export() {
5286 let cmd = Commands::Export {
5287 file: PathBuf::from("model.apr"),
5288 format: "gguf".to_string(),
5289 output: PathBuf::from("out.gguf"),
5290 quantize: None,
5291 };
5292 let paths = extract_model_paths(&cmd);
5293 assert_eq!(paths, vec![PathBuf::from("model.apr")]);
5294 }
5295
5296 #[test]
5298 fn test_extract_paths_convert() {
5299 let cmd = Commands::Convert {
5300 file: PathBuf::from("model.apr"),
5301 quantize: Some("q4k".to_string()),
5302 compress: None,
5303 output: PathBuf::from("out.apr"),
5304 force: false,
5305 };
5306 let paths = extract_model_paths(&cmd);
5307 assert_eq!(paths, vec![PathBuf::from("model.apr")]);
5308 }
5309
5310 #[test]
5312 fn test_extract_paths_check() {
5313 let cmd = Commands::Check {
5314 file: PathBuf::from("model.gguf"),
5315 no_gpu: false,
5316 };
5317 let paths = extract_model_paths(&cmd);
5318 assert_eq!(paths, vec![PathBuf::from("model.gguf")]);
5319 }
5320
5321 #[test]
5323 fn test_extract_paths_trace() {
5324 let cmd = Commands::Trace {
5325 file: PathBuf::from("model.apr"),
5326 layer: None,
5327 reference: None,
5328 json: false,
5329 verbose: false,
5330 payload: false,
5331 diff: false,
5332 interactive: false,
5333 };
5334 let paths = extract_model_paths(&cmd);
5335 assert_eq!(paths, vec![PathBuf::from("model.apr")]);
5336 }
5337
5338 #[test]
5340 fn test_extract_paths_probar() {
5341 let cmd = Commands::Probar {
5342 file: PathBuf::from("model.apr"),
5343 output: PathBuf::from("./probar-export"),
5344 format: "both".to_string(),
5345 golden: None,
5346 layer: None,
5347 };
5348 let paths = extract_model_paths(&cmd);
5349 assert_eq!(paths, vec![PathBuf::from("model.apr")]);
5350 }
5351
5352 #[test]
5354 fn test_extract_paths_compare_hf() {
5355 let cmd = Commands::CompareHf {
5356 file: PathBuf::from("model.apr"),
5357 hf: "openai/whisper-tiny".to_string(),
5358 tensor: None,
5359 threshold: 1e-5,
5360 json: false,
5361 };
5362 let paths = extract_model_paths(&cmd);
5363 assert_eq!(paths, vec![PathBuf::from("model.apr")]);
5364 }
5365
5366 #[test]
5368 fn test_extract_paths_chat() {
5369 let cmd = Commands::Chat {
5370 file: PathBuf::from("model.gguf"),
5371 temperature: 0.7,
5372 top_p: 0.9,
5373 max_tokens: 512,
5374 system: None,
5375 inspect: false,
5376 no_gpu: false,
5377 gpu: false,
5378 trace: false,
5379 trace_steps: None,
5380 trace_verbose: false,
5381 trace_output: None,
5382 trace_level: "basic".to_string(),
5383 profile: false,
5384 };
5385 let paths = extract_model_paths(&cmd);
5386 assert_eq!(paths, vec![PathBuf::from("model.gguf")]);
5387 }
5388
5389 #[test]
5391 fn test_extract_paths_eval() {
5392 let cmd = Commands::Eval {
5393 file: PathBuf::from("model.gguf"),
5394 dataset: "wikitext-2".to_string(),
5395 text: None,
5396 max_tokens: 512,
5397 threshold: 20.0,
5398 };
5399 let paths = extract_model_paths(&cmd);
5400 assert_eq!(paths, vec![PathBuf::from("model.gguf")]);
5401 }
5402
5403 #[test]
5405 fn test_extract_paths_profile() {
5406 let cmd = Commands::Profile {
5407 file: PathBuf::from("model.apr"),
5408 granular: false,
5409 format: "human".to_string(),
5410 focus: None,
5411 detect_naive: false,
5412 threshold: 10.0,
5413 compare_hf: None,
5414 energy: false,
5415 perf_grade: false,
5416 callgraph: false,
5417 fail_on_naive: false,
5418 output: None,
5419 ci: false,
5420 assert_throughput: None,
5421 assert_p99: None,
5422 assert_p50: None,
5423 warmup: 3,
5424 measure: 10,
5425 tokens: 32,
5426 ollama: false,
5427 no_gpu: false,
5428 compare: None,
5429 };
5430 let paths = extract_model_paths(&cmd);
5431 assert_eq!(paths, vec![PathBuf::from("model.apr")]);
5432 }
5433
5434 #[test]
5436 fn test_extract_paths_import_hf_url() {
5437 let cmd = Commands::Import {
5438 source: "hf://openai/whisper-tiny".to_string(),
5439 output: Some(PathBuf::from("whisper.apr")),
5440 arch: "auto".to_string(),
5441 quantize: None,
5442 strict: false,
5443 preserve_q4k: false,
5444 tokenizer: None,
5445 enforce_provenance: false,
5446 };
5447 let paths = extract_model_paths(&cmd);
5448 assert!(
5449 paths.is_empty(),
5450 "hf:// URLs should not be validated locally for import"
5451 );
5452 }
5453
5454 #[test]
5456 fn test_extract_paths_import_nonexistent_local() {
5457 let cmd = Commands::Import {
5458 source: "/tmp/nonexistent_model_abc123.gguf".to_string(),
5459 output: None,
5460 arch: "auto".to_string(),
5461 quantize: None,
5462 strict: false,
5463 preserve_q4k: false,
5464 tokenizer: None,
5465 enforce_provenance: false,
5466 };
5467 let paths = extract_model_paths(&cmd);
5468 assert!(
5469 paths.is_empty(),
5470 "Non-existent local paths return empty for import"
5471 );
5472 }
5473
5474 #[test]
5476 fn test_extract_paths_tui_with_file() {
5477 let cmd = Commands::Tui {
5478 file: Some(PathBuf::from("model.apr")),
5479 };
5480 let paths = extract_model_paths(&cmd);
5481 assert_eq!(paths, vec![PathBuf::from("model.apr")]);
5482 }
5483
5484 #[test]
5486 fn test_extract_paths_tui_no_file() {
5487 let cmd = Commands::Tui { file: None };
5488 let paths = extract_model_paths(&cmd);
5489 assert!(paths.is_empty());
5490 }
5491
5492 #[test]
5494 fn test_extract_paths_cbtop_with_model_path() {
5495 let cmd = Commands::Cbtop {
5496 model: None,
5497 attach: None,
5498 model_path: Some(PathBuf::from("model.gguf")),
5499 headless: false,
5500 json: false,
5501 output: None,
5502 ci: false,
5503 throughput: None,
5504 brick_score: None,
5505 warmup: 10,
5506 iterations: 100,
5507 speculative: false,
5508 speculation_k: 4,
5509 draft_model: None,
5510 concurrent: 1,
5511 simulated: false,
5512 };
5513 let paths = extract_model_paths(&cmd);
5514 assert_eq!(paths, vec![PathBuf::from("model.gguf")]);
5515 }
5516
5517 #[test]
5519 fn test_extract_paths_cbtop_no_model_path() {
5520 let cmd = Commands::Cbtop {
5521 model: Some("qwen2.5-coder".to_string()),
5522 attach: None,
5523 model_path: None,
5524 headless: false,
5525 json: false,
5526 output: None,
5527 ci: false,
5528 throughput: None,
5529 brick_score: None,
5530 warmup: 10,
5531 iterations: 100,
5532 speculative: false,
5533 speculation_k: 4,
5534 draft_model: None,
5535 concurrent: 1,
5536 simulated: false,
5537 };
5538 let paths = extract_model_paths(&cmd);
5539 assert!(paths.is_empty());
5540 }
5541
5542 #[test]
5544 fn test_extract_paths_diff_exempt() {
5545 let cmd = Commands::Diff {
5546 file1: PathBuf::from("a.apr"),
5547 file2: PathBuf::from("b.apr"),
5548 weights: false,
5549 values: false,
5550 filter: None,
5551 limit: 10,
5552 transpose_aware: false,
5553 json: false,
5554 };
5555 let paths = extract_model_paths(&cmd);
5556 assert!(paths.is_empty(), "Diff is a diagnostic command (exempt)");
5557 }
5558
5559 #[test]
5561 fn test_extract_paths_hex_exempt() {
5562 let cmd = Commands::Hex {
5563 file: PathBuf::from("model.apr"),
5564 tensor: None,
5565 limit: 64,
5566 stats: false,
5567 list: false,
5568 json: false,
5569 header: false,
5570 blocks: false,
5571 distribution: false,
5572 contract: false,
5573 entropy: false,
5574 raw: false,
5575 offset: "0".to_string(),
5576 width: 16,
5577 };
5578 let paths = extract_model_paths(&cmd);
5579 assert!(paths.is_empty(), "Hex is a diagnostic command (exempt)");
5580 }
5581
5582 #[test]
5584 fn test_extract_paths_tree_exempt() {
5585 let cmd = Commands::Tree {
5586 file: PathBuf::from("model.apr"),
5587 filter: None,
5588 format: "ascii".to_string(),
5589 sizes: false,
5590 depth: None,
5591 };
5592 let paths = extract_model_paths(&cmd);
5593 assert!(paths.is_empty(), "Tree is a diagnostic command (exempt)");
5594 }
5595
5596 #[test]
5598 fn test_extract_paths_flow_exempt() {
5599 let cmd = Commands::Flow {
5600 file: PathBuf::from("model.apr"),
5601 layer: None,
5602 component: "full".to_string(),
5603 verbose: false,
5604 };
5605 let paths = extract_model_paths(&cmd);
5606 assert!(paths.is_empty(), "Flow is a diagnostic command (exempt)");
5607 }
5608
5609 #[test]
5611 fn test_extract_paths_publish_exempt() {
5612 let cmd = Commands::Publish {
5613 directory: PathBuf::from("/tmp/models"),
5614 repo_id: "org/repo".to_string(),
5615 model_name: None,
5616 license: "mit".to_string(),
5617 pipeline_tag: "text-generation".to_string(),
5618 library_name: None,
5619 tags: None,
5620 message: None,
5621 dry_run: false,
5622 };
5623 let paths = extract_model_paths(&cmd);
5624 assert!(paths.is_empty(), "Publish is a diagnostic command (exempt)");
5625 }
5626
5627 #[test]
5629 fn test_extract_paths_tune_exempt() {
5630 let cmd = Commands::Tune {
5631 file: Some(PathBuf::from("model.apr")),
5632 method: "auto".to_string(),
5633 rank: None,
5634 vram: 16.0,
5635 plan: false,
5636 model: None,
5637 freeze_base: false,
5638 train_data: None,
5639 json: false,
5640 };
5641 let paths = extract_model_paths(&cmd);
5642 assert!(paths.is_empty(), "Tune is a diagnostic command (exempt)");
5643 }
5644
5645 #[test]
5647 fn test_extract_paths_pull_exempt() {
5648 let cmd = Commands::Pull {
5649 model_ref: "hf://org/repo".to_string(),
5650 force: false,
5651 };
5652 let paths = extract_model_paths(&cmd);
5653 assert!(paths.is_empty(), "Pull is a diagnostic command (exempt)");
5654 }
5655
5656 #[test]
5658 fn test_extract_paths_rm_exempt() {
5659 let cmd = Commands::Rm {
5660 model_ref: "model-name".to_string(),
5661 };
5662 let paths = extract_model_paths(&cmd);
5663 assert!(paths.is_empty(), "Rm is a diagnostic command (exempt)");
5664 }
5665
5666 #[test]
5668 fn test_extract_paths_canary_exempt() {
5669 let cmd = Commands::Canary {
5670 command: CanaryCommands::Check {
5671 file: PathBuf::from("model.apr"),
5672 canary: PathBuf::from("canary.json"),
5673 },
5674 };
5675 let paths = extract_model_paths(&cmd);
5676 assert!(paths.is_empty(), "Canary is a diagnostic command (exempt)");
5677 }
5678
5679 #[test]
5681 fn test_extract_paths_oracle_exempt() {
5682 let cmd = Commands::Oracle {
5683 source: Some("model.gguf".to_string()),
5684 family: None,
5685 size: None,
5686 compliance: false,
5687 tensors: false,
5688 stats: false,
5689 explain: false,
5690 kernels: false,
5691 validate: false,
5692 full: false,
5693 };
5694 let paths = extract_model_paths(&cmd);
5695 assert!(paths.is_empty(), "Oracle is a diagnostic command (exempt)");
5696 }
5697
5698 #[test]
5700 fn test_extract_paths_showcase_exempt() {
5701 let cmd = Commands::Showcase {
5702 auto_verify: false,
5703 step: None,
5704 tier: "small".to_string(),
5705 model_dir: PathBuf::from("./models"),
5706 baseline: "llama-cpp,ollama".to_string(),
5707 zram: false,
5708 runs: 30,
5709 gpu: false,
5710 json: false,
5711 verbose: false,
5712 quiet: false,
5713 };
5714 let paths = extract_model_paths(&cmd);
5715 assert!(
5716 paths.is_empty(),
5717 "Showcase is a diagnostic command (exempt)"
5718 );
5719 }
5720
5721 #[test]
5723 fn test_extract_paths_rosetta_convert() {
5724 let cmd = Commands::Rosetta {
5725 action: RosettaCommands::Convert {
5726 source: PathBuf::from("model.gguf"),
5727 target: PathBuf::from("out.safetensors"),
5728 quantize: None,
5729 verify: false,
5730 json: false,
5731 tokenizer: None,
5732 },
5733 };
5734 let paths = extract_model_paths(&cmd);
5735 assert_eq!(paths, vec![PathBuf::from("model.gguf")]);
5736 }
5737
5738 #[test]
5740 fn test_extract_paths_rosetta_chain() {
5741 let cmd = Commands::Rosetta {
5742 action: RosettaCommands::Chain {
5743 source: PathBuf::from("model.gguf"),
5744 formats: vec!["safetensors".to_string(), "apr".to_string()],
5745 work_dir: PathBuf::from("/tmp"),
5746 json: false,
5747 },
5748 };
5749 let paths = extract_model_paths(&cmd);
5750 assert_eq!(paths, vec![PathBuf::from("model.gguf")]);
5751 }
5752
5753 #[test]
5755 fn test_extract_paths_rosetta_verify() {
5756 let cmd = Commands::Rosetta {
5757 action: RosettaCommands::Verify {
5758 source: PathBuf::from("model.apr"),
5759 intermediate: "safetensors".to_string(),
5760 tolerance: 1e-5,
5761 json: false,
5762 },
5763 };
5764 let paths = extract_model_paths(&cmd);
5765 assert_eq!(paths, vec![PathBuf::from("model.apr")]);
5766 }
5767
5768 #[test]
5770 fn test_extract_paths_rosetta_compare_inference() {
5771 let cmd = Commands::Rosetta {
5772 action: RosettaCommands::CompareInference {
5773 model_a: PathBuf::from("model_a.gguf"),
5774 model_b: PathBuf::from("model_b.apr"),
5775 prompt: "test".to_string(),
5776 max_tokens: 5,
5777 temperature: 0.0,
5778 tolerance: 0.1,
5779 json: false,
5780 },
5781 };
5782 let paths = extract_model_paths(&cmd);
5783 assert_eq!(
5784 paths,
5785 vec![PathBuf::from("model_a.gguf"), PathBuf::from("model_b.apr")]
5786 );
5787 }
5788
5789 #[test]
5791 fn test_extract_paths_rosetta_inspect_exempt() {
5792 let cmd = Commands::Rosetta {
5793 action: RosettaCommands::Inspect {
5794 file: PathBuf::from("model.gguf"),
5795 hexdump: false,
5796 json: false,
5797 },
5798 };
5799 let paths = extract_model_paths(&cmd);
5800 assert!(
5801 paths.is_empty(),
5802 "Rosetta Inspect is a diagnostic command (exempt)"
5803 );
5804 }
5805
5806 #[test]
5808 fn test_extract_paths_rosetta_diff_tensors_exempt() {
5809 let cmd = Commands::Rosetta {
5810 action: RosettaCommands::DiffTensors {
5811 model_a: PathBuf::from("a.gguf"),
5812 model_b: PathBuf::from("b.apr"),
5813 mismatches_only: false,
5814 show_values: 0,
5815 filter: None,
5816 json: false,
5817 },
5818 };
5819 let paths = extract_model_paths(&cmd);
5820 assert!(
5821 paths.is_empty(),
5822 "Rosetta DiffTensors is a diagnostic command (exempt)"
5823 );
5824 }
5825
5826 #[test]
5828 fn test_extract_paths_rosetta_fingerprint_exempt() {
5829 let cmd = Commands::Rosetta {
5830 action: RosettaCommands::Fingerprint {
5831 model: PathBuf::from("model.gguf"),
5832 model_b: None,
5833 output: None,
5834 filter: None,
5835 verbose: false,
5836 json: false,
5837 },
5838 };
5839 let paths = extract_model_paths(&cmd);
5840 assert!(
5841 paths.is_empty(),
5842 "Rosetta Fingerprint is a diagnostic command (exempt)"
5843 );
5844 }
5845
5846 #[test]
5848 fn test_extract_paths_rosetta_validate_stats_exempt() {
5849 let cmd = Commands::Rosetta {
5850 action: RosettaCommands::ValidateStats {
5851 model: PathBuf::from("model.apr"),
5852 reference: None,
5853 fingerprints: None,
5854 threshold: 3.0,
5855 strict: false,
5856 json: false,
5857 },
5858 };
5859 let paths = extract_model_paths(&cmd);
5860 assert!(
5861 paths.is_empty(),
5862 "Rosetta ValidateStats is a diagnostic command (exempt)"
5863 );
5864 }
5865
5866 #[test]
5872 fn test_validate_contract_multiple_nonexistent() {
5873 let paths = vec![
5874 PathBuf::from("/tmp/nonexistent_a.apr"),
5875 PathBuf::from("/tmp/nonexistent_b.gguf"),
5876 PathBuf::from("/tmp/nonexistent_c.safetensors"),
5877 ];
5878 let result = validate_model_contract(&paths);
5879 assert!(result.is_ok(), "All non-existent paths should be skipped");
5880 }
5881
5882 #[test]
5884 fn test_validate_contract_mixed_nonexistent() {
5885 let paths = vec![
5886 PathBuf::from("/tmp/does_not_exist_xyz.apr"),
5887 PathBuf::from("/tmp/also_missing_123.gguf"),
5888 ];
5889 let result = validate_model_contract(&paths);
5890 assert!(
5891 result.is_ok(),
5892 "Mixed non-existent paths should all be skipped"
5893 );
5894 }
5895
5896 fn make_cli(command: Commands) -> Cli {
5902 Cli {
5903 command: Box::new(command),
5904 json: false,
5905 verbose: false,
5906 quiet: false,
5907 offline: false,
5908 skip_contract: true, }
5910 }
5911
5912 #[test]
5914 fn test_execute_inspect_file_not_found() {
5915 let cli = make_cli(Commands::Inspect {
5916 file: PathBuf::from("/tmp/nonexistent_model_inspect_test.apr"),
5917 vocab: false,
5918 filters: false,
5919 weights: false,
5920 json: false,
5921 });
5922 let result = execute_command(&cli);
5923 assert!(
5924 result.is_err(),
5925 "Inspect should fail with non-existent file"
5926 );
5927 }
5928
5929 #[test]
5931 fn test_execute_debug_file_not_found() {
5932 let cli = make_cli(Commands::Debug {
5933 file: PathBuf::from("/tmp/nonexistent_model_debug_test.apr"),
5934 drama: false,
5935 hex: false,
5936 strings: false,
5937 limit: 256,
5938 });
5939 let result = execute_command(&cli);
5940 assert!(result.is_err(), "Debug should fail with non-existent file");
5941 }
5942
5943 #[test]
5945 fn test_execute_validate_file_not_found() {
5946 let cli = make_cli(Commands::Validate {
5947 file: PathBuf::from("/tmp/nonexistent_model_validate_test.apr"),
5948 quality: false,
5949 strict: false,
5950 min_score: None,
5951 });
5952 let result = execute_command(&cli);
5953 assert!(
5954 result.is_err(),
5955 "Validate should fail with non-existent file"
5956 );
5957 }
5958
5959 #[test]
5961 fn test_execute_diff_file_not_found() {
5962 let cli = make_cli(Commands::Diff {
5963 file1: PathBuf::from("/tmp/nonexistent_model_diff1.apr"),
5964 file2: PathBuf::from("/tmp/nonexistent_model_diff2.apr"),
5965 weights: false,
5966 values: false,
5967 filter: None,
5968 limit: 10,
5969 transpose_aware: false,
5970 json: false,
5971 });
5972 let result = execute_command(&cli);
5973 assert!(result.is_err(), "Diff should fail with non-existent files");
5974 }
5975
5976 #[test]
5978 fn test_execute_tensors_file_not_found() {
5979 let cli = make_cli(Commands::Tensors {
5980 file: PathBuf::from("/tmp/nonexistent_model_tensors_test.apr"),
5981 stats: false,
5982 filter: None,
5983 limit: 0,
5984 json: false,
5985 });
5986 let result = execute_command(&cli);
5987 assert!(
5988 result.is_err(),
5989 "Tensors should fail with non-existent file"
5990 );
5991 }
5992
5993 #[test]
5995 fn test_execute_lint_file_not_found() {
5996 let cli = make_cli(Commands::Lint {
5997 file: PathBuf::from("/tmp/nonexistent_model_lint_test.apr"),
5998 });
5999 let result = execute_command(&cli);
6000 assert!(result.is_err(), "Lint should fail with non-existent file");
6001 }
6002
6003 #[test]
6005 fn test_execute_trace_file_not_found() {
6006 let cli = make_cli(Commands::Trace {
6007 file: PathBuf::from("/tmp/nonexistent_model_trace_test.apr"),
6008 layer: None,
6009 reference: None,
6010 json: false,
6011 verbose: false,
6012 payload: false,
6013 diff: false,
6014 interactive: false,
6015 });
6016 let result = execute_command(&cli);
6017 assert!(result.is_err(), "Trace should fail with non-existent file");
6018 }
6019
6020 #[test]
6022 fn test_execute_export_file_not_found() {
6023 let cli = make_cli(Commands::Export {
6024 file: PathBuf::from("/tmp/nonexistent_model_export_test.apr"),
6025 format: "safetensors".to_string(),
6026 output: PathBuf::from("/tmp/out.safetensors"),
6027 quantize: None,
6028 });
6029 let result = execute_command(&cli);
6030 assert!(result.is_err(), "Export should fail with non-existent file");
6031 }
6032
6033 #[test]
6035 fn test_execute_convert_file_not_found() {
6036 let cli = make_cli(Commands::Convert {
6037 file: PathBuf::from("/tmp/nonexistent_model_convert_test.apr"),
6038 quantize: None,
6039 compress: None,
6040 output: PathBuf::from("/tmp/out.apr"),
6041 force: false,
6042 });
6043 let result = execute_command(&cli);
6044 assert!(
6045 result.is_err(),
6046 "Convert should fail with non-existent file"
6047 );
6048 }
6049
6050 #[test]
6052 fn test_execute_hex_file_not_found() {
6053 let cli = make_cli(Commands::Hex {
6054 file: PathBuf::from("/tmp/nonexistent_model_hex_test.apr"),
6055 tensor: None,
6056 limit: 64,
6057 stats: false,
6058 list: false,
6059 json: false,
6060 header: false,
6061 blocks: false,
6062 distribution: false,
6063 contract: false,
6064 entropy: false,
6065 raw: false,
6066 offset: String::new(),
6067 width: 16,
6068 });
6069 let result = execute_command(&cli);
6070 assert!(result.is_err(), "Hex should fail with non-existent file");
6071 }
6072
6073 #[test]
6075 fn test_execute_tree_file_not_found() {
6076 let cli = make_cli(Commands::Tree {
6077 file: PathBuf::from("/tmp/nonexistent_model_tree_test.apr"),
6078 filter: None,
6079 format: "ascii".to_string(),
6080 sizes: false,
6081 depth: None,
6082 });
6083 let result = execute_command(&cli);
6084 assert!(result.is_err(), "Tree should fail with non-existent file");
6085 }
6086
6087 #[test]
6089 fn test_execute_flow_file_not_found() {
6090 let cli = make_cli(Commands::Flow {
6091 file: PathBuf::from("/tmp/nonexistent_model_flow_test.apr"),
6092 layer: None,
6093 component: "full".to_string(),
6094 verbose: false,
6095 });
6096 let result = execute_command(&cli);
6097 assert!(result.is_err(), "Flow should fail with non-existent file");
6098 }
6099
6100 #[test]
6102 fn test_execute_probar_file_not_found() {
6103 let cli = make_cli(Commands::Probar {
6104 file: PathBuf::from("/tmp/nonexistent_model_probar_test.apr"),
6105 output: PathBuf::from("/tmp/probar-out"),
6106 format: "both".to_string(),
6107 golden: None,
6108 layer: None,
6109 });
6110 let result = execute_command(&cli);
6111 assert!(result.is_err(), "Probar should fail with non-existent file");
6112 }
6113
6114 #[test]
6116 fn test_execute_check_file_not_found() {
6117 let cli = make_cli(Commands::Check {
6118 file: PathBuf::from("/tmp/nonexistent_model_check_test.apr"),
6119 no_gpu: true,
6120 });
6121 let result = execute_command(&cli);
6122 assert!(result.is_err(), "Check should fail with non-existent file");
6123 }
6124
6125 #[test]
6127 fn test_execute_list_succeeds() {
6128 let cli = make_cli(Commands::List);
6129 let result = execute_command(&cli);
6131 assert!(result.is_ok(), "List should succeed without arguments");
6132 }
6133
6134 #[test]
6136 fn test_execute_explain_no_args() {
6137 let cli = make_cli(Commands::Explain {
6138 code: None,
6139 file: None,
6140 tensor: None,
6141 });
6142 let result = execute_command(&cli);
6144 assert!(result.is_ok(), "Explain with no args should succeed");
6145 }
6146
6147 #[test]
6149 fn test_execute_explain_with_code() {
6150 let cli = make_cli(Commands::Explain {
6151 code: Some("E001".to_string()),
6152 file: None,
6153 tensor: None,
6154 });
6155 let result = execute_command(&cli);
6156 assert!(result.is_ok(), "Explain with error code should succeed");
6158 }
6159
6160 #[test]
6162 fn test_execute_tune_plan_no_file() {
6163 let cli = make_cli(Commands::Tune {
6164 file: None,
6165 method: "auto".to_string(),
6166 rank: None,
6167 vram: 16.0,
6168 plan: true,
6169 model: Some("7B".to_string()),
6170 freeze_base: false,
6171 train_data: None,
6172 json: false,
6173 });
6174 let result = execute_command(&cli);
6175 assert!(
6177 result.is_ok(),
6178 "Tune --plan --model 7B should succeed without file"
6179 );
6180 }
6181
6182 #[test]
6186 fn test_execute_qa_all_skips_succeeds() {
6187 let cli = make_cli(Commands::Qa {
6188 file: PathBuf::from("/tmp/nonexistent_model_qa_test.gguf"),
6189 assert_tps: None,
6190 assert_speedup: None,
6191 assert_gpu_speedup: None,
6192 skip_golden: true,
6193 skip_throughput: true,
6194 skip_ollama: true,
6195 skip_gpu_speedup: true,
6196 skip_contract: true,
6197 skip_format_parity: true,
6198 skip_ptx_parity: true,
6199 safetensors_path: None,
6200 iterations: 1,
6201 warmup: 0,
6202 max_tokens: 1,
6203 json: false,
6204 verbose: false,
6205 min_executed: None,
6206 previous_report: None,
6207 regression_threshold: None,
6208 skip_gpu_state: false,
6209 skip_metadata: true,
6210 });
6211 let result = execute_command(&cli);
6212 assert!(
6213 result.is_ok(),
6214 "Qa with all gates skipped should succeed even with non-existent file"
6215 );
6216 }
6217
6218 #[test]
6220 fn test_execute_qa_with_gates_file_not_found() {
6221 let cli = make_cli(Commands::Qa {
6222 file: PathBuf::from("/tmp/nonexistent_model_qa_gates_test.gguf"),
6223 assert_tps: None,
6224 assert_speedup: None,
6225 assert_gpu_speedup: None,
6226 skip_golden: false, skip_throughput: true,
6228 skip_ollama: true,
6229 skip_gpu_speedup: true,
6230 skip_contract: true,
6231 skip_format_parity: true,
6232 skip_ptx_parity: true,
6233 safetensors_path: None,
6234 iterations: 1,
6235 warmup: 0,
6236 max_tokens: 1,
6237 json: false,
6238 verbose: false,
6239 min_executed: None,
6240 previous_report: None,
6241 regression_threshold: None,
6242 skip_gpu_state: false,
6243 skip_metadata: true,
6244 });
6245 let result = execute_command(&cli);
6246 assert!(
6247 result.is_err(),
6248 "Qa with golden gate enabled should fail with non-existent file"
6249 );
6250 }
6251
6252 #[test]
6254 fn test_execute_import_invalid_source() {
6255 let cli = make_cli(Commands::Import {
6256 source: "/tmp/nonexistent_model_import_test.gguf".to_string(),
6257 output: None,
6258 arch: "auto".to_string(),
6259 quantize: None,
6260 strict: false,
6261 preserve_q4k: false,
6262 tokenizer: None,
6263 enforce_provenance: false,
6264 });
6265 let result = execute_command(&cli);
6266 assert!(
6267 result.is_err(),
6268 "Import should fail with non-existent source file"
6269 );
6270 }
6271
6272 #[test]
6278 fn test_chat_flag_chatml_wrapping_logic() {
6279 let prompt = "What is the meaning of life?";
6282 let chat = true;
6283
6284 let effective_prompt = if chat {
6285 Some(format!(
6286 "<|im_start|>user\n{}<|im_end|>\n<|im_start|>assistant\n",
6287 prompt
6288 ))
6289 } else {
6290 Some(prompt.to_string())
6291 };
6292
6293 assert!(effective_prompt
6294 .as_ref()
6295 .expect("prompt should exist")
6296 .starts_with("<|im_start|>user\n"));
6297 assert!(effective_prompt
6298 .as_ref()
6299 .expect("prompt should exist")
6300 .ends_with("<|im_start|>assistant\n"));
6301 assert!(effective_prompt
6302 .as_ref()
6303 .expect("prompt should exist")
6304 .contains("What is the meaning of life?"));
6305 }
6306
6307 #[test]
6309 fn test_no_chat_flag_passthrough() {
6310 let prompt = Some("Hello world".to_string());
6311 let chat = false;
6312
6313 let effective_prompt = if chat {
6314 prompt
6315 .as_ref()
6316 .map(|p| format!("<|im_start|>user\n{}<|im_end|>\n<|im_start|>assistant\n", p))
6317 } else {
6318 prompt.clone()
6319 };
6320
6321 assert_eq!(effective_prompt, Some("Hello world".to_string()));
6322 }
6323
6324 #[test]
6326 fn test_chat_flag_no_prompt() {
6327 let prompt: Option<String> = None;
6328 let chat = true;
6329
6330 let effective_prompt = if chat {
6331 prompt
6332 .as_ref()
6333 .map(|p| format!("<|im_start|>user\n{}<|im_end|>\n<|im_start|>assistant\n", p))
6334 } else {
6335 prompt.clone()
6336 };
6337
6338 assert!(effective_prompt.is_none());
6339 }
6340
6341 #[test]
6347 fn test_trace_payload_shorthand_logic() {
6348 let trace = false;
6349 let trace_payload = true;
6350 let trace_level = "basic".to_string();
6351
6352 let effective_trace = trace || trace_payload;
6353 let effective_trace_level = if trace_payload {
6354 "payload"
6355 } else {
6356 trace_level.as_str()
6357 };
6358
6359 assert!(effective_trace);
6360 assert_eq!(effective_trace_level, "payload");
6361 }
6362
6363 #[test]
6365 fn test_no_trace_payload_preserves_settings() {
6366 let trace = true;
6367 let trace_payload = false;
6368 let trace_level = "layer".to_string();
6369
6370 let effective_trace = trace || trace_payload;
6371 let effective_trace_level = if trace_payload {
6372 "payload"
6373 } else {
6374 trace_level.as_str()
6375 };
6376
6377 assert!(effective_trace);
6378 assert_eq!(effective_trace_level, "layer");
6379 }
6380
6381 #[test]
6383 fn test_no_trace_no_trace_payload() {
6384 let trace = false;
6385 let trace_payload = false;
6386 let trace_level = "basic".to_string();
6387
6388 let effective_trace = trace || trace_payload;
6389 let effective_trace_level = if trace_payload {
6390 "payload"
6391 } else {
6392 trace_level.as_str()
6393 };
6394
6395 assert!(!effective_trace);
6396 assert_eq!(effective_trace_level, "basic");
6397 }
6398
6399 #[test]
6405 fn test_verbose_local_true_global_false() {
6406 let local_verbose = true;
6407 let global_verbose = false;
6408 let effective_verbose = local_verbose || global_verbose;
6409 assert!(effective_verbose);
6410 }
6411
6412 #[test]
6414 fn test_verbose_local_false_global_true() {
6415 let local_verbose = false;
6416 let global_verbose = true;
6417 let effective_verbose = local_verbose || global_verbose;
6418 assert!(effective_verbose);
6419 }
6420
6421 #[test]
6423 fn test_verbose_both_false() {
6424 let local_verbose = false;
6425 let global_verbose = false;
6426 let effective_verbose = local_verbose || global_verbose;
6427 assert!(!effective_verbose);
6428 }
6429
6430 #[test]
6432 fn test_verbose_both_true() {
6433 let local_verbose = true;
6434 let global_verbose = true;
6435 let effective_verbose = local_verbose || global_verbose;
6436 assert!(effective_verbose);
6437 }
6438
6439 #[test]
6444 fn test_verbose_inheritance_run_global() {
6445 let args = vec!["apr", "--verbose", "run", "model.gguf", "--prompt", "test"];
6446 let cli = parse_cli(args).expect("Failed to parse");
6447 assert!(cli.verbose);
6448 match *cli.command {
6449 Commands::Run { verbose, .. } => {
6450 let effective = verbose || cli.verbose;
6453 assert!(effective);
6454 }
6455 _ => panic!("Expected Run command"),
6456 }
6457 }
6458
6459 #[test]
6463 fn test_verbose_inheritance_run_local() {
6464 let args = vec!["apr", "run", "model.gguf", "--prompt", "test", "-v"];
6465 let cli = parse_cli(args).expect("Failed to parse");
6466 match *cli.command {
6468 Commands::Run { verbose, .. } => {
6469 let effective = verbose || cli.verbose;
6470 assert!(effective, "effective verbose should be true");
6471 }
6472 _ => panic!("Expected Run command"),
6473 }
6474 }
6475
6476 #[test]
6482 fn test_parse_run_gpu_nogpu_conflict() {
6483 let args = vec![
6484 "apr",
6485 "run",
6486 "model.gguf",
6487 "--prompt",
6488 "test",
6489 "--gpu",
6490 "--no-gpu",
6491 ];
6492 let result = parse_cli(args);
6493 assert!(result.is_err(), "--gpu and --no-gpu should conflict in Run");
6494 }
6495
6496 #[test]
6498 fn test_parse_run_gpu_only() {
6499 let args = vec!["apr", "run", "model.gguf", "--prompt", "test", "--gpu"];
6500 let cli = parse_cli(args).expect("Failed to parse");
6501 match *cli.command {
6502 Commands::Run { gpu, no_gpu, .. } => {
6503 assert!(gpu);
6504 assert!(!no_gpu);
6505 }
6506 _ => panic!("Expected Run command"),
6507 }
6508 }
6509
6510 #[test]
6516 fn test_missing_serve_file() {
6517 let args = vec!["apr", "serve"];
6518 let result = parse_cli(args);
6519 assert!(result.is_err(), "serve requires FILE");
6520 }
6521
6522 #[test]
6524 fn test_missing_diff_second_file() {
6525 let args = vec!["apr", "diff", "model1.apr"];
6526 let result = parse_cli(args);
6527 assert!(result.is_err(), "diff requires two files");
6528 }
6529
6530 #[test]
6532 fn test_missing_export_output() {
6533 let args = vec!["apr", "export", "model.apr"];
6534 let result = parse_cli(args);
6535 assert!(result.is_err(), "export requires -o/--output");
6536 }
6537
6538 #[test]
6540 fn test_missing_convert_output() {
6541 let args = vec!["apr", "convert", "model.apr"];
6542 let result = parse_cli(args);
6543 assert!(result.is_err(), "convert requires -o/--output");
6544 }
6545
6546 #[test]
6548 fn test_missing_merge_files() {
6549 let args = vec!["apr", "merge", "model1.apr", "-o", "out.apr"];
6550 let result = parse_cli(args);
6551 assert!(result.is_err(), "merge requires at least 2 files");
6552 }
6553
6554 #[test]
6556 fn test_missing_publish_repo_id() {
6557 let args = vec!["apr", "publish", "/tmp/models"];
6558 let result = parse_cli(args);
6559 assert!(result.is_err(), "publish requires REPO_ID");
6560 }
6561
6562 #[test]
6564 fn test_missing_pull_model_ref() {
6565 let args = vec!["apr", "pull"];
6566 let result = parse_cli(args);
6567 assert!(result.is_err(), "pull requires MODEL");
6568 }
6569
6570 #[test]
6572 fn test_missing_rm_model_ref() {
6573 let args = vec!["apr", "rm"];
6574 let result = parse_cli(args);
6575 assert!(result.is_err(), "rm requires MODEL");
6576 }
6577
6578 #[test]
6580 fn test_missing_compare_hf_hf_arg() {
6581 let args = vec!["apr", "compare-hf", "model.apr"];
6582 let result = parse_cli(args);
6583 assert!(result.is_err(), "compare-hf requires --hf");
6584 }
6585
6586 #[test]
6588 fn test_missing_canary_create_input() {
6589 let args = vec![
6590 "apr",
6591 "canary",
6592 "create",
6593 "model.apr",
6594 "--output",
6595 "canary.json",
6596 ];
6597 let result = parse_cli(args);
6598 assert!(result.is_err(), "canary create requires --input");
6599 }
6600
6601 #[test]
6603 fn test_missing_canary_check_canary() {
6604 let args = vec!["apr", "canary", "check", "model.apr"];
6605 let result = parse_cli(args);
6606 assert!(result.is_err(), "canary check requires --canary");
6607 }
6608
6609 #[test]
6616 fn test_execute_with_contract_gate_nonexistent() {
6617 let cli = Cli {
6618 command: Box::new(Commands::Inspect {
6619 file: PathBuf::from("/tmp/nonexistent_contract_test.apr"),
6620 vocab: false,
6621 filters: false,
6622 weights: false,
6623 json: false,
6624 }),
6625 json: false,
6626 verbose: false,
6627 quiet: false,
6628 offline: false,
6629 skip_contract: false, };
6631 let result = execute_command(&cli);
6634 assert!(
6635 result.is_err(),
6636 "Should still fail from command execution, not contract"
6637 );
6638 }
6639
6640 #[test]
6642 fn test_execute_list_with_contract_enabled() {
6643 let cli = Cli {
6644 command: Box::new(Commands::List),
6645 json: false,
6646 verbose: false,
6647 quiet: false,
6648 offline: false,
6649 skip_contract: false, };
6651 let result = execute_command(&cli);
6652 assert!(result.is_ok(), "List should succeed with contract enabled");
6653 }
6654
6655 #[test]
6661 fn test_execute_rosetta_inspect_file_not_found() {
6662 let cli = make_cli(Commands::Rosetta {
6663 action: RosettaCommands::Inspect {
6664 file: PathBuf::from("/tmp/nonexistent_rosetta_inspect.gguf"),
6665 hexdump: false,
6666 json: false,
6667 },
6668 });
6669 let result = execute_command(&cli);
6670 assert!(
6671 result.is_err(),
6672 "Rosetta inspect should fail with non-existent file"
6673 );
6674 }
6675
6676 #[test]
6678 fn test_execute_rosetta_convert_file_not_found() {
6679 let cli = make_cli(Commands::Rosetta {
6680 action: RosettaCommands::Convert {
6681 source: PathBuf::from("/tmp/nonexistent_rosetta_convert.gguf"),
6682 target: PathBuf::from("/tmp/out.safetensors"),
6683 quantize: None,
6684 verify: false,
6685 json: false,
6686 tokenizer: None,
6687 },
6688 });
6689 let result = execute_command(&cli);
6690 assert!(
6691 result.is_err(),
6692 "Rosetta convert should fail with non-existent source"
6693 );
6694 }
6695
6696 #[test]
6698 fn test_execute_rosetta_fingerprint_file_not_found() {
6699 let cli = make_cli(Commands::Rosetta {
6700 action: RosettaCommands::Fingerprint {
6701 model: PathBuf::from("/tmp/nonexistent_rosetta_fingerprint.gguf"),
6702 model_b: None,
6703 output: None,
6704 filter: None,
6705 verbose: false,
6706 json: false,
6707 },
6708 });
6709 let result = execute_command(&cli);
6710 assert!(
6711 result.is_err(),
6712 "Rosetta fingerprint should fail with non-existent file"
6713 );
6714 }
6715
6716 #[test]
6718 fn test_execute_bench_file_not_found() {
6719 let cli = make_cli(Commands::Bench {
6720 file: PathBuf::from("/tmp/nonexistent_model_bench_test.gguf"),
6721 warmup: 1,
6722 iterations: 1,
6723 max_tokens: 1,
6724 prompt: None,
6725 fast: false,
6726 brick: None,
6727 });
6728 let result = execute_command(&cli);
6729 assert!(result.is_err(), "Bench should fail with non-existent file");
6730 }
6731
6732 #[test]
6734 fn test_execute_eval_file_not_found() {
6735 let cli = make_cli(Commands::Eval {
6736 file: PathBuf::from("/tmp/nonexistent_model_eval_test.gguf"),
6737 dataset: "wikitext-2".to_string(),
6738 text: None,
6739 max_tokens: 32,
6740 threshold: 20.0,
6741 });
6742 let result = execute_command(&cli);
6743 assert!(result.is_err(), "Eval should fail with non-existent file");
6744 }
6745
6746 #[test]
6748 fn test_execute_profile_file_not_found() {
6749 let cli = make_cli(Commands::Profile {
6750 file: PathBuf::from("/tmp/nonexistent_model_profile_test.apr"),
6751 granular: false,
6752 format: "human".to_string(),
6753 focus: None,
6754 detect_naive: false,
6755 threshold: 10.0,
6756 compare_hf: None,
6757 energy: false,
6758 perf_grade: false,
6759 callgraph: false,
6760 fail_on_naive: false,
6761 output: None,
6762 ci: false,
6763 assert_throughput: None,
6764 assert_p99: None,
6765 assert_p50: None,
6766 warmup: 3,
6767 measure: 10,
6768 tokens: 32,
6769 ollama: false,
6770 no_gpu: false,
6771 compare: None,
6772 });
6773 let result = execute_command(&cli);
6774 assert!(
6775 result.is_err(),
6776 "Profile should fail with non-existent file"
6777 );
6778 }
6779
6780 #[test]
6782 fn test_execute_compare_hf_file_not_found() {
6783 let cli = make_cli(Commands::CompareHf {
6784 file: PathBuf::from("/tmp/nonexistent_model_compare_hf_test.apr"),
6785 hf: "openai/whisper-tiny".to_string(),
6786 tensor: None,
6787 threshold: 1e-5,
6788 json: false,
6789 });
6790 let result = execute_command(&cli);
6791 assert!(
6792 result.is_err(),
6793 "CompareHf should fail with non-existent file"
6794 );
6795 }
6796
6797 #[test]
6799 fn test_execute_canary_check_file_not_found() {
6800 let cli = make_cli(Commands::Canary {
6801 command: CanaryCommands::Check {
6802 file: PathBuf::from("/tmp/nonexistent_canary_check.apr"),
6803 canary: PathBuf::from("/tmp/nonexistent_canary.json"),
6804 },
6805 });
6806 let result = execute_command(&cli);
6807 assert!(
6808 result.is_err(),
6809 "Canary check should fail with non-existent file"
6810 );
6811 }
6812
6813 #[test]
6815 fn test_execute_publish_dir_not_found() {
6816 let cli = make_cli(Commands::Publish {
6817 directory: PathBuf::from("/tmp/nonexistent_publish_dir_test"),
6818 repo_id: "test/test".to_string(),
6819 model_name: None,
6820 license: "mit".to_string(),
6821 pipeline_tag: "text-generation".to_string(),
6822 library_name: None,
6823 tags: None,
6824 message: None,
6825 dry_run: true, });
6827 let result = execute_command(&cli);
6828 assert!(
6829 result.is_err(),
6830 "Publish should fail with non-existent directory"
6831 );
6832 }
6833
6834 #[test]
6840 fn test_parse_run_defaults() {
6841 let args = vec!["apr", "run", "model.gguf"];
6842 let cli = parse_cli(args).expect("Failed to parse");
6843 match *cli.command {
6844 Commands::Run {
6845 max_tokens,
6846 stream,
6847 format,
6848 no_gpu,
6849 gpu,
6850 offline,
6851 benchmark,
6852 trace,
6853 trace_payload,
6854 trace_verbose,
6855 trace_level,
6856 profile,
6857 chat,
6858 verbose,
6859 prompt,
6860 input,
6861 language,
6862 task,
6863 trace_steps,
6864 trace_output,
6865 ..
6866 } => {
6867 assert_eq!(max_tokens, 32);
6868 assert!(!stream);
6869 assert_eq!(format, "text");
6870 assert!(!no_gpu);
6871 assert!(!gpu);
6872 assert!(!offline);
6873 assert!(!benchmark);
6874 assert!(!trace);
6875 assert!(!trace_payload);
6876 assert!(!trace_verbose);
6877 assert_eq!(trace_level, "basic");
6878 assert!(!profile);
6879 assert!(!chat);
6880 assert!(!verbose);
6881 assert!(prompt.is_none());
6882 assert!(input.is_none());
6883 assert!(language.is_none());
6884 assert!(task.is_none());
6885 assert!(trace_steps.is_none());
6886 assert!(trace_output.is_none());
6887 }
6888 _ => panic!("Expected Run command"),
6889 }
6890 }
6891
6892 #[test]
6894 fn test_parse_serve_defaults() {
6895 let args = vec!["apr", "serve", "model.apr"];
6896 let cli = parse_cli(args).expect("Failed to parse");
6897 match *cli.command {
6898 Commands::Serve {
6899 port,
6900 host,
6901 no_cors,
6902 no_metrics,
6903 no_gpu,
6904 gpu,
6905 batch,
6906 trace,
6907 trace_level,
6908 profile,
6909 ..
6910 } => {
6911 assert_eq!(port, 8080);
6912 assert_eq!(host, "127.0.0.1");
6913 assert!(!no_cors);
6914 assert!(!no_metrics);
6915 assert!(!no_gpu);
6916 assert!(!gpu);
6917 assert!(!batch);
6918 assert!(!trace);
6919 assert_eq!(trace_level, "basic");
6920 assert!(!profile);
6921 }
6922 _ => panic!("Expected Serve command"),
6923 }
6924 }
6925
6926 #[test]
6928 fn test_parse_bench_defaults() {
6929 let args = vec!["apr", "bench", "model.gguf"];
6930 let cli = parse_cli(args).expect("Failed to parse");
6931 match *cli.command {
6932 Commands::Bench {
6933 warmup,
6934 iterations,
6935 max_tokens,
6936 prompt,
6937 fast,
6938 brick,
6939 ..
6940 } => {
6941 assert_eq!(warmup, 3);
6942 assert_eq!(iterations, 5);
6943 assert_eq!(max_tokens, 32);
6944 assert!(prompt.is_none());
6945 assert!(!fast);
6946 assert!(brick.is_none());
6947 }
6948 _ => panic!("Expected Bench command"),
6949 }
6950 }
6951
6952 #[test]
6954 fn test_parse_cbtop_defaults() {
6955 let args = vec!["apr", "cbtop"];
6956 let cli = parse_cli(args).expect("Failed to parse");
6957 match *cli.command {
6958 Commands::Cbtop {
6959 model,
6960 attach,
6961 model_path,
6962 headless,
6963 json,
6964 output,
6965 ci,
6966 throughput,
6967 brick_score,
6968 warmup,
6969 iterations,
6970 speculative,
6971 speculation_k,
6972 draft_model,
6973 concurrent,
6974 simulated,
6975 } => {
6976 assert!(model.is_none());
6977 assert!(attach.is_none());
6978 assert!(model_path.is_none());
6979 assert!(!headless);
6980 assert!(!json);
6981 assert!(output.is_none());
6982 assert!(!ci);
6983 assert!(throughput.is_none());
6984 assert!(brick_score.is_none());
6985 assert_eq!(warmup, 10);
6986 assert_eq!(iterations, 100);
6987 assert!(!speculative);
6988 assert_eq!(speculation_k, 4);
6989 assert!(draft_model.is_none());
6990 assert_eq!(concurrent, 1);
6991 assert!(!simulated);
6992 }
6993 _ => panic!("Expected Cbtop command"),
6994 }
6995 }
6996
6997 #[test]
6999 fn test_parse_profile_defaults() {
7000 let args = vec!["apr", "profile", "model.apr"];
7001 let cli = parse_cli(args).expect("Failed to parse");
7002 match *cli.command {
7003 Commands::Profile {
7004 granular,
7005 format,
7006 focus,
7007 detect_naive,
7008 threshold,
7009 compare_hf,
7010 energy,
7011 perf_grade,
7012 callgraph,
7013 fail_on_naive,
7014 output,
7015 ci,
7016 assert_throughput,
7017 assert_p99,
7018 assert_p50,
7019 warmup,
7020 measure,
7021 ..
7022 } => {
7023 assert!(!granular);
7024 assert_eq!(format, "human");
7025 assert!(focus.is_none());
7026 assert!(!detect_naive);
7027 assert!((threshold - 10.0).abs() < f64::EPSILON);
7028 assert!(compare_hf.is_none());
7029 assert!(!energy);
7030 assert!(!perf_grade);
7031 assert!(!callgraph);
7032 assert!(!fail_on_naive);
7033 assert!(output.is_none());
7034 assert!(!ci);
7035 assert!(assert_throughput.is_none());
7036 assert!(assert_p99.is_none());
7037 assert!(assert_p50.is_none());
7038 assert_eq!(warmup, 3);
7039 assert_eq!(measure, 10);
7040 }
7041 _ => panic!("Expected Profile command"),
7042 }
7043 }
7044
7045 #[test]
7047 fn test_parse_qa_defaults() {
7048 let args = vec!["apr", "qa", "model.gguf"];
7049 let cli = parse_cli(args).expect("Failed to parse");
7050 match *cli.command {
7051 Commands::Qa {
7052 assert_tps,
7053 assert_speedup,
7054 assert_gpu_speedup,
7055 skip_golden,
7056 skip_throughput,
7057 skip_ollama,
7058 skip_gpu_speedup,
7059 skip_contract,
7060 skip_format_parity,
7061 safetensors_path,
7062 iterations,
7063 warmup,
7064 max_tokens,
7065 json,
7066 verbose,
7067 ..
7068 } => {
7069 assert!(assert_tps.is_none());
7070 assert!(assert_speedup.is_none());
7071 assert!(assert_gpu_speedup.is_none());
7072 assert!(!skip_golden);
7073 assert!(!skip_throughput);
7074 assert!(!skip_ollama);
7075 assert!(!skip_gpu_speedup);
7076 assert!(!skip_contract);
7077 assert!(!skip_format_parity);
7078 assert!(safetensors_path.is_none());
7079 assert_eq!(iterations, 10);
7080 assert_eq!(warmup, 3);
7081 assert_eq!(max_tokens, 32);
7082 assert!(!json);
7083 assert!(!verbose);
7084 }
7085 _ => panic!("Expected Qa command"),
7086 }
7087 }
7088
7089 #[test]
7091 fn test_parse_chat_defaults() {
7092 let args = vec!["apr", "chat", "model.gguf"];
7093 let cli = parse_cli(args).expect("Failed to parse");
7094 match *cli.command {
7095 Commands::Chat {
7096 temperature,
7097 top_p,
7098 max_tokens,
7099 system,
7100 inspect,
7101 no_gpu,
7102 gpu,
7103 trace,
7104 trace_steps,
7105 trace_verbose,
7106 trace_output,
7107 trace_level,
7108 profile,
7109 ..
7110 } => {
7111 assert!((temperature - 0.7).abs() < f32::EPSILON);
7112 assert!((top_p - 0.9).abs() < f32::EPSILON);
7113 assert_eq!(max_tokens, 512);
7114 assert!(system.is_none());
7115 assert!(!inspect);
7116 assert!(!no_gpu);
7117 assert!(!gpu);
7118 assert!(!trace);
7119 assert!(trace_steps.is_none());
7120 assert!(!trace_verbose);
7121 assert!(trace_output.is_none());
7122 assert_eq!(trace_level, "basic");
7123 assert!(!profile);
7124 }
7125 _ => panic!("Expected Chat command"),
7126 }
7127 }
7128}