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