1use std::{
21 collections::{BTreeMap, BTreeSet},
22 ffi::OsString,
23 fs,
24 io::Write,
25 path::{Path, PathBuf},
26 time::Instant,
27};
28
29use serde::{Deserialize, Serialize};
30
31use crate::{
32 evidence_archive::write_archive,
33 frontend_protocol::validate_frontend_report_request,
34 go_instrumenter::{RUNTIME_ALIAS, RUNTIME_IMPORT, rewrite},
35 go_project::{PreparedGoProject, go_integrity_inputs, module_path, prepare_go_project},
36 go_test_harness::{instrument_test_file, probe_array_file, synthesized_harness},
37 integrity::{FrontendIntegrityInputs, create_explicit_run_integrity},
38 lifecycle::{
39 ProjectLock, finalize_published_run, note_kept_evidence, publish_run,
40 recover_abandoned_runs, remove_stored_tree_deferred,
41 },
42 orchestration::{ExecutionPhase, ExecutionPlan, PhaseKind, execute_plan},
43 owned_evidence::{
44 OwnedRunInputs, OwnedTestOutcome, build_frontend_run, go_coverage_model, go_declaration,
45 merge_evidence, read_evidence,
46 },
47 process_supervision::{CommandSpec, SupervisionOptions},
48 run_store::{RawEvidenceMetadata, RunMetadata, RunTimings},
49 workspace::{canonicalize_simplified, prepare_cached_workspace, recover_cached_workspace},
50};
51
52const RUNTIME_DIRECTORY: &str = "supercov_runtime";
58
59const RUNTIME_SOURCE: &str = include_str!("../runtime-assets/go/supercov/supercov.go");
60
61const PROBE_FILE: &str = "supercov_probes.go";
63
64const HARNESS_FILE: &str = "supercov_generated_test.go";
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase", deny_unknown_fields)]
69pub struct DirectGoRunRequest {
70 pub root: PathBuf,
71 pub command: Vec<String>,
72 pub run_id: String,
73 pub started_at: String,
74 #[serde(default)]
83 pub exact_attribution: bool,
84}
85
86#[derive(Debug, Clone, PartialEq)]
87pub struct DirectGoRunResult {
88 pub run_id: String,
89 pub run_directory: PathBuf,
90 pub exit_code: i32,
91 pub tests: usize,
92 pub unattributed: usize,
98 pub source_files: usize,
99 pub unparseable_sources: usize,
102 pub unparseable_tests: usize,
106 pub packages: usize,
107 pub recovered_runs: Vec<String>,
108 pub metadata: RunMetadata,
109}
110
111fn elapsed_ms(started: Instant) -> f64 {
112 (started.elapsed().as_secs_f64() * 10_000.0).round() / 10.0
113}
114
115#[derive(Debug, Clone)]
117struct GoTestPackage {
118 directory: String,
121 evidence: PathBuf,
122 declared_in: BTreeMap<String, String>,
124 unattributed: BTreeSet<String>,
127}
128
129struct InstrumentedWorkspace {
130 project: PreparedGoProject,
131 packages: Vec<GoTestPackage>,
132 unparseable_tests: Vec<(String, String)>,
135}
136
137fn directory_of(relative: &str) -> String {
138 match relative.rsplit_once('/') {
139 Some((directory, _)) => directory.to_owned(),
140 None => ".".to_owned(),
141 }
142}
143
144fn package_name(source: &str) -> Option<String> {
151 let bytes = source.as_bytes();
152 let mut at = 0;
153 while at < bytes.len() {
154 let rest = &source[at..];
155 if rest.starts_with("//") {
156 at += rest.find('\n').map_or(rest.len(), |end| end + 1);
157 } else if rest.starts_with("/*") {
158 at += rest.find("*/").map_or(rest.len(), |end| end + 2);
160 } else if bytes[at].is_ascii_whitespace() {
161 at += 1;
162 } else {
163 return rest
164 .strip_prefix("package")
165 .and_then(|rest| rest.strip_prefix(|c: char| c.is_ascii_whitespace()))
166 .and_then(|rest| rest.split_whitespace().next())
167 .map(str::to_owned);
168 }
169 }
170 None
171}
172
173fn read(path: &Path) -> Result<String, String> {
174 fs::read_to_string(path).map_err(|error| format!("{}: {error}", path.display()))
175}
176
177fn write(path: &Path, contents: &str) -> Result<(), String> {
178 if let Some(parent) = path.parent() {
179 fs::create_dir_all(parent).map_err(|error| format!("{}: {error}", parent.display()))?;
180 }
181 fs::write(path, contents).map_err(|error| format!("{}: {error}", path.display()))
182}
183
184fn evidence_name(directory: &str) -> String {
187 let slug = directory
188 .chars()
189 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
190 .collect::<String>();
191 format!("{slug}.bin")
192}
193
194fn instrument_workspace(
197 workspace: &Path,
198 evidence_directory: &Path,
199 serialised: bool,
200 roots: Option<&crate::source_discovery::ExplicitSourceRoots>,
201) -> Result<InstrumentedWorkspace, String> {
202 let mut project = prepare_go_project(workspace, roots)?;
203 let mut unparseable_tests: Vec<(String, String)> = Vec::new();
204
205 let members = crate::go_project::workspace_modules(workspace);
209 let directories = if members.is_empty() {
210 vec![".".to_owned()]
211 } else {
212 members.into_iter().collect::<Vec<_>>()
213 };
214 let mut runtimes: Vec<(String, String)> = Vec::new();
215 for directory in &directories {
216 let at = workspace.join(directory);
217 let module = module_path(&at).ok_or_else(|| {
218 format!(
219 "{}: no module path in go.mod, so Supercov cannot name the package its runtime is imported from",
220 at.display()
221 )
222 })?;
223 write(
224 &at.join(RUNTIME_DIRECTORY).join("supercov.go"),
225 RUNTIME_SOURCE,
226 )?;
227 runtimes.push((directory.clone(), format!("{module}/{RUNTIME_DIRECTORY}")));
228 }
229 runtimes.sort_by_key(|(directory, _)| std::cmp::Reverse(directory.len()));
231 let import_for = |relative: &str| -> &str {
232 runtimes
233 .iter()
234 .find(|(directory, _)| {
235 directory == "."
236 || relative == directory
237 || relative.starts_with(&format!("{directory}/"))
238 })
239 .map(|(_, import)| import.as_str())
240 .unwrap_or_default()
241 };
242
243 let probe_count = project
247 .probes
248 .keys()
249 .max()
250 .map_or(0, |highest| *highest as usize + 1);
251
252 let mut source_packages: BTreeMap<String, String> = BTreeMap::new();
253 for (relative, instrumented) in &project.instrumented {
254 let instrumented = instrumented.replace(RUNTIME_IMPORT, import_for(relative));
255 let path = workspace.join(relative);
256 if let Some(name) = package_name(&instrumented) {
257 source_packages
258 .entry(directory_of(relative))
259 .or_insert(name);
260 }
261 write(&path, &instrumented)?;
262 }
263 for (directory, package) in &source_packages {
264 write(
265 &workspace.join(directory).join(PROBE_FILE),
266 &probe_array_file(
267 package,
268 RUNTIME_ALIAS,
269 import_for(&format!("{directory}/x.go")),
270 probe_count,
271 ),
272 )?;
273 }
274
275 let mut by_directory: BTreeMap<String, Vec<&String>> = BTreeMap::new();
276 for relative in &project.files.tests {
277 by_directory
278 .entry(directory_of(relative))
279 .or_default()
280 .push(relative);
281 }
282
283 let mut packages = Vec::new();
284 for (directory, tests) in by_directory {
285 let evidence = evidence_directory.join(evidence_name(&directory));
286 let evidence_literal = evidence.to_string_lossy().replace('\\', "\\\\");
287 let mut declared_in = BTreeMap::new();
288 let mut unattributed = BTreeSet::new();
289 let mut declares_test_main = false;
290 let mut harness_package: Option<String> = None;
294 for relative in tests {
295 let path = workspace.join(relative);
296 let source = read(&path)?;
297 let Some(name) = package_name(&source) else {
298 continue;
299 };
300 let internal = source_packages.get(&directory) == Some(&name);
304 if internal || harness_package.is_none() {
305 harness_package = Some(name);
306 }
307 let built = !build_ignored(&source);
312 let file =
313 match instrument_test_file(&source, RUNTIME_ALIAS, &evidence_literal, serialised) {
314 Ok(file) => file,
315 Err(error) => {
316 project.manifest.limitations.push(
324 crate::go_project::unparseable_limitation(relative, &error.to_string()),
325 );
326 unparseable_tests.push((relative.clone(), error.to_string()));
327 declares_test_main |= built && declares_test_main_textually(&source);
333 continue;
334 }
335 };
336 declares_test_main |= built && file.declares_test_main;
337 for test in &file.tests {
338 declared_in.insert(test.clone(), relative.clone());
339 }
340 unattributed.extend(file.unattributed.iter().cloned());
341 if !file.edits.is_empty() {
342 write(
343 &path,
344 &rewrite(&source, &file.edits).replace(RUNTIME_IMPORT, import_for(relative)),
345 )?;
346 }
347 }
348 let Some(package) = harness_package else {
349 continue;
350 };
351 write(
352 &workspace.join(&directory).join(HARNESS_FILE),
353 &synthesized_harness(
354 &package,
355 RUNTIME_ALIAS,
356 import_for(&format!("{directory}/x.go")),
357 probe_count,
358 &project.decision_widths,
359 &evidence_literal,
360 declares_test_main,
361 ),
362 )?;
363 packages.push(GoTestPackage {
364 directory,
365 evidence,
366 declared_in,
367 unattributed,
368 });
369 }
370 Ok(InstrumentedWorkspace {
371 project,
372 packages,
373 unparseable_tests,
374 })
375}
376
377fn build_ignored(source: &str) -> bool {
387 let header = if source.starts_with("package ") {
391 ""
394 } else {
395 source
396 .find("\npackage ")
397 .map_or(source, |clause| &source[..clause])
398 };
399 header
400 .lines()
401 .map(str::trim)
402 .filter_map(|line| {
403 line.strip_prefix("//go:build")
404 .or_else(|| line.strip_prefix("// +build"))
406 })
407 .any(|constraint| {
408 constraint
409 .split(|c: char| !(c.is_alphanumeric() || c == '_'))
410 .any(|term| term == "ignore")
411 })
412}
413
414fn declares_test_main_textually(source: &str) -> bool {
421 source.split("func TestMain").skip(1).any(|rest| {
422 rest.trim_start()
423 .strip_prefix('(')
424 .is_some_and(|arguments| arguments.contains("testing.M"))
425 })
426}
427
428fn command_run_in_order(command: &[String]) -> (Vec<String>, bool) {
435 if command
436 .iter()
437 .any(|argument| argument == "-parallel" || argument.starts_with("-parallel="))
438 {
439 return (command.to_vec(), false);
442 }
443 let mut updated = command.to_vec();
444 let position = updated
445 .iter()
446 .position(|argument| argument == "test")
447 .map_or(updated.len(), |index| index + 1);
448 updated.insert(position, "-parallel=1".into());
449 (updated, true)
450}
451
452fn command_with_fresh_results(command: &[String]) -> (Vec<String>, bool) {
453 if command
454 .iter()
455 .any(|argument| argument == "-count" || argument.starts_with("-count="))
456 {
457 return (command.to_vec(), false);
458 }
459 let mut updated = command.to_vec();
460 let position = updated
462 .iter()
463 .position(|argument| argument == "test")
464 .map_or(updated.len(), |index| index + 1);
465 updated.insert(position, "-count=1".into());
466 (updated, true)
467}
468
469pub fn current_go_integrity(
471 root: &Path,
472 command: &[String],
473 source_roots: Option<&[String]>,
474) -> Result<crate::run_store::RunIntegrity, String> {
475 let root = canonicalize_simplified(root).map_err(|error| error.to_string())?;
476 let files = crate::go_project::discover_go_files(&root)?;
477 let mut inputs = go_integrity_inputs(&files, command);
478 crate::source_discovery::fold_roots_into_configuration(
479 &mut inputs.execution_configuration,
480 source_roots,
481 );
482 create_explicit_run_integrity(&root, &inputs, &FrontendIntegrityInputs::embedded_go())
483 .map_err(|error| error.to_string())
484}
485
486pub fn run_direct_go(
487 request: &DirectGoRunRequest,
488 diagnostics: &mut dyn Write,
489) -> Result<DirectGoRunResult, String> {
490 if request.command.is_empty() {
491 return Err("test command must not be empty".into());
492 }
493 let total_started = Instant::now();
494 let initialization_started = Instant::now();
495 let root = canonicalize_simplified(&request.root)
496 .map_err(|error| format!("{}: {error}", request.root.display()))?;
497 let mut lock = ProjectLock::acquire(&root, &request.run_id, &request.started_at)
498 .map_err(|error| error.to_string())?;
499 let initialization_ms = elapsed_ms(initialization_started);
500 let work_directory = root.join(".supercov/work").join(&request.run_id);
501 let measured = std::cell::Cell::new(false);
505 let result = (|| {
506 let recovered_runs = recover_abandoned_runs(&root, &request.started_at)
507 .map_err(|error| error.to_string())?;
508 if !recovered_runs.is_empty() {
509 writeln!(
510 diagnostics,
511 "[supercov] recovered abandoned run(s): {}",
512 recovered_runs.join(", ")
513 )
514 .map_err(|error| error.to_string())?;
515 }
516
517 let adapter_started = Instant::now();
518 let files = crate::go_project::discover_go_files(&root)?;
519 let source_roots = crate::source_discovery::configured_source_roots(
520 &std::env::vars().collect::<std::collections::BTreeMap<_, _>>(),
521 );
522 let mut integrity_inputs = go_integrity_inputs(&files, &request.command);
523 crate::source_discovery::fold_roots_into_configuration(
524 &mut integrity_inputs.execution_configuration,
525 source_roots.as_deref(),
526 );
527 let source_inputs =
528 crate::source_capture::capture(&root, "go", integrity_inputs.assertion_paths())?;
529 let integrity = create_explicit_run_integrity(
530 &root,
531 &integrity_inputs,
532 &FrontendIntegrityInputs::embedded_go(),
533 )
534 .map_err(|error| error.to_string())?;
535
536 let workspace_started = Instant::now();
537 recover_cached_workspace(&root, &lock).map_err(|error| error.to_string())?;
538 let workspace =
539 prepare_cached_workspace(&root, &lock, &[]).map_err(|error| error.to_string())?;
540 let evidence_directory = work_directory.join("go/evidence");
541 fs::create_dir_all(&evidence_directory).map_err(|error| error.to_string())?;
542 let (command, serialised) = if request.exact_attribution {
545 command_run_in_order(&request.command)
546 } else {
547 (request.command.clone(), false)
548 };
549 let ambient = std::env::vars().collect::<std::collections::BTreeMap<_, _>>();
550 let roots = crate::source_discovery::ExplicitSourceRoots::from_environment_in(
551 &root, &workspace, &ambient,
552 )
553 .map_err(|error| error.to_string())?;
554 let instrumented =
555 instrument_workspace(&workspace, &evidence_directory, serialised, roots.as_ref())?;
556 let workspace_preparation_ms = elapsed_ms(workspace_started);
557 let adapter_setup_ms = (elapsed_ms(adapter_started) - workspace_preparation_ms).max(0.0);
558 writeln!(
559 diagnostics,
560 "[supercov] detected Go; instrumenting {} source file(s) across {} test package(s) in isolated workspace {}",
561 instrumented.project.instrumented.len(),
562 instrumented.packages.len(),
563 workspace.display()
564 )
565 .map_err(|error| error.to_string())?;
566 for (file, reason) in &instrumented.project.unparseable {
567 writeln!(
568 diagnostics,
569 "[supercov] could not parse {file}: {reason}; it carries no obligations"
570 )
571 .map_err(|error| error.to_string())?;
572 }
573 for (file, reason) in &instrumented.unparseable_tests {
574 writeln!(
575 diagnostics,
576 "[supercov] could not parse {file}: {reason}; the tests it declares run unattributed and what they reach counts run-wide"
577 )
578 .map_err(|error| error.to_string())?;
579 }
580 if instrumented.packages.is_empty() {
581 return Err(
582 "no Go test packages were found, so a run would measure nothing: Supercov needs at least one _test.go file"
583 .into(),
584 );
585 }
586 if instrumented.project.instrumented.is_empty()
593 && !instrumented.project.files.sources.is_empty()
594 {
595 return Err(format!(
596 "none of the {} Go source file(s) could be parsed, so the run would measure a denominator of nothing: {}",
597 instrumented.project.files.sources.len(),
598 instrumented
599 .project
600 .unparseable
601 .iter()
602 .map(|(file, reason)| format!("{file}: {reason}"))
603 .collect::<Vec<_>>()
604 .join("; ")
605 ));
606 }
607
608 if serialised {
609 writeln!(
610 diagnostics,
611 "[supercov] added -parallel=1 for --exact-attribution: a test that calls t.Parallel() can only be credited with what it reached when nothing else is running. Your suite runs in order, which is slower."
612 )
613 .map_err(|error| error.to_string())?;
614 }
615 let (command, forced_fresh) = command_with_fresh_results(&command);
616 if forced_fresh {
617 writeln!(
618 diagnostics,
619 "[supercov] added -count=1: `go test` caches passing packages, and a cached package does not run, so it would record no coverage"
620 )
621 .map_err(|error| error.to_string())?;
622 }
623 let test_started = Instant::now();
624 let plan = ExecutionPlan {
625 preparation: Vec::new(),
626 test: ExecutionPhase {
627 name: "test".into(),
628 kind: PhaseKind::Test,
629 command: CommandSpec {
630 program: command[0].clone().into(),
631 arguments: command[1..].iter().map(OsString::from).collect(),
632 cwd: workspace.clone(),
633 environment: None,
634 captured_output: None,
635 },
636 },
637 };
638 let options = SupervisionOptions::from_environment().map_err(|error| error.to_string())?;
639 let execution = execute_plan(&plan, options, diagnostics, |_, _| Ok(()))
640 .map_err(|error| error.to_string())?;
641 let test_command_ms = elapsed_ms(test_started);
642 if let Some(signal) = execution.interrupted_signal {
643 return Err(format!(
644 "the test command was interrupted by {signal:?}; no run was published"
645 ));
646 }
647 let exit_code = execution.exit_code;
648
649 let publication_started = Instant::now();
650 let mut parts = Vec::new();
651 let mut outcomes = Vec::new();
652 let mut silent = Vec::new();
653 for package in &instrumented.packages {
654 let Ok(bytes) = fs::read(&package.evidence) else {
655 silent.push(package.directory.clone());
656 continue;
657 };
658 let evidence = read_evidence(&bytes)
659 .map_err(|error| format!("{}: {error}", package.evidence.display()))?;
660 for test in &evidence.tests {
661 outcomes.push(OwnedTestOutcome {
662 name: test.name.clone(),
663 runner: test.runner.clone(),
664 package: package.directory.clone(),
665 file: package.declared_in.get(&test.name).cloned(),
666 status: test.status.clone(),
667 attributed: !test.unattributed,
668 });
669 }
670 parts.push(evidence);
671 }
672 if !silent.is_empty() {
673 writeln!(
674 diagnostics,
675 "[supercov] {} test package(s) wrote no evidence and are absent from this run: {}",
676 silent.len(),
677 silent.join(", ")
678 )
679 .map_err(|error| error.to_string())?;
680 }
681 let evidence = merge_evidence(parts);
682 let unattributed = instrumented
683 .packages
684 .iter()
685 .map(|package| package.unattributed.len())
686 .sum::<usize>();
687 let reached_something = evidence.global.iter().any(|total| *total != 0);
694 if outcomes.is_empty() && !reached_something {
695 let because = if unattributed > 0 {
696 format!(
697 "; {unattributed} test(s) ran without being able to announce themselves (t.Parallel, Example or Fuzz) and reached nothing measured either"
698 )
699 } else {
700 String::new()
701 };
702 return Err(format!(
703 "no Go test recorded evidence (the command exited {exit_code}); a run that measured nothing is not published{because}"
704 ));
705 }
706 measured.set(true);
707 let run = build_frontend_run(OwnedRunInputs {
708 declaration: go_declaration(),
709 environment: "go",
710 manifest: &instrumented.project.manifest,
711 probes: &instrumented.project.probes,
712 evidence: &evidence,
713 outcomes: &outcomes,
714 run_id: &request.run_id,
715 generated_at: &request.started_at,
716 test_exit_code: exit_code,
717 coverage_model: go_coverage_model(),
718 })
719 .map_err(|error| error.to_string())?;
720 validate_frontend_report_request(&run.declaration, &run.request)
724 .map_err(|error| error.to_string())?;
725 let archive_path = work_directory.join("evidence.raw.gz");
726 let raw = write_archive(
727 crate::source_capture::append(
728 run.archive_entries().map_err(|error| error.to_string())?,
729 &source_inputs,
730 )?,
731 &archive_path,
732 )
733 .map_err(|error| error.to_string())?;
734 let evidence_publication_ms = elapsed_ms(publication_started);
735
736 let timings = RunTimings {
737 initialization_ms,
738 workspace_preparation_ms,
739 adapter_setup_ms,
740 instrumented_build_ms: 0.0,
741 test_command_ms,
742 evidence_publication_ms,
743 };
744 let metadata = RunMetadata {
745 id: request.run_id.clone(),
746 started_at: request.started_at.clone(),
747 duration_ms: elapsed_ms(total_started),
748 command: request.command.clone(),
749 test_exit_code: Some(exit_code),
750 integrity,
751 raw_evidence: RawEvidenceMetadata {
752 schema_version: raw.schema_version,
753 format: raw.format.into(),
754 file: raw.file.into(),
755 files: raw.files,
756 uncompressed_bytes: raw.uncompressed_bytes,
757 compressed_bytes: raw.compressed_bytes,
758 },
759 isolated_build: Some(true),
760 instrumented_build_cache: None,
761 timings: Some(timings),
762 merged: None,
763 parents: None,
764 source_roots: source_roots.clone(),
765 };
766 let run_directory =
767 publish_run(&root, &metadata, &archive_path).map_err(|error| error.to_string())?;
768 finalize_published_run(&root, &request.run_id).map_err(|error| error.to_string())?;
769 Ok(DirectGoRunResult {
770 run_id: request.run_id.clone(),
771 run_directory,
772 exit_code,
773 tests: outcomes
774 .iter()
775 .map(|outcome| outcome.name.as_str())
776 .collect::<BTreeSet<_>>()
777 .len(),
778 unattributed,
779 source_files: instrumented.project.instrumented.len(),
780 unparseable_sources: instrumented.project.unparseable.len(),
781 unparseable_tests: instrumented.unparseable_tests.len(),
782 packages: instrumented.packages.len(),
783 recovered_runs,
784 metadata,
785 })
786 })();
787 let result = result.map_err(|error| {
788 if !measured.get() {
789 return error;
790 }
791 note_kept_evidence(
792 &root,
793 &work_directory.join("go/evidence"),
794 &request.run_id,
795 error,
796 )
797 });
798 if result.is_err() {
799 let _ = remove_stored_tree_deferred(&root, &work_directory);
800 }
801 let release = lock.release().map_err(|error| error.to_string());
802 match (result, release) {
803 (Ok(result), Ok(())) => Ok(result),
804 (Err(error), _) => Err(error),
805 (Ok(_), Err(error)) => Err(error),
806 }
807}
808
809#[cfg(test)]
810mod tests {
811 use super::*;
812
813 #[test]
814 fn the_package_clause_is_found_past_whatever_precedes_it() {
815 assert_eq!(package_name("package main\n").as_deref(), Some("main"));
819 assert_eq!(
820 package_name("//go:build linux\n\n// Copyright.\npackage worker\n").as_deref(),
821 Some("worker")
822 );
823 assert_eq!(
824 package_name("/*\nA package comment\nspanning lines.\n*/\npackage doc\n").as_deref(),
825 Some("doc")
826 );
827 assert_eq!(
828 package_name("/* one line */ package inline\n").as_deref(),
829 Some("inline")
830 );
831 assert_eq!(
833 package_name("package api // the public surface\n").as_deref(),
834 Some("api")
835 );
836 assert_eq!(package_name("import \"fmt\"\n"), None);
837 }
838
839 #[test]
840 fn caching_is_disabled_unless_the_author_already_chose() {
841 let (command, forced) =
845 command_with_fresh_results(&["go".into(), "test".into(), "./...".into()]);
846 assert!(forced);
847 assert_eq!(command, ["go", "test", "-count=1", "./..."]);
848
849 let (command, forced) =
851 command_with_fresh_results(&["go".into(), "test".into(), "-count=3".into()]);
852 assert!(!forced);
853 assert_eq!(command, ["go", "test", "-count=3"]);
854 }
855
856 #[test]
857 fn each_package_gets_an_evidence_file_of_its_own() {
858 assert_ne!(evidence_name("internal/auth"), evidence_name("internal/db"));
861 assert_eq!(evidence_name("."), "_.bin");
862 assert!(!evidence_name("internal/auth").contains('/'));
863 }
864
865 #[test]
866 fn only_the_constraint_nothing_ever_defines_is_read_alone() {
867 assert!(build_ignored("//go:build ignore\n\npackage p\n"));
873 assert!(build_ignored("// +build ignore\n\npackage p\n"));
874 assert!(build_ignored(
875 "// Copyright.\n\n//go:build ignore\n\npackage p\n"
876 ));
877 assert!(build_ignored("//go:build ignore || linux\n\npackage p\n"));
879
880 assert!(!build_ignored("//go:build linux\n\npackage p\n"));
882 assert!(!build_ignored("//go:build !windows\n\npackage p\n"));
883 assert!(!build_ignored("package p\n"));
884 assert!(!build_ignored("//go:build ignored\n\npackage p\n"));
886 assert!(!build_ignored("//go:build ignore_me\n\npackage p\n"));
887 assert!(!build_ignored(
891 "package p\n\n//go:build ignore\n\nfunc f() {}\n"
892 ));
893 assert!(!build_ignored(
894 "package p\n\nvar doc = \"//go:build ignore\"\n"
895 ));
896 }
897
898 #[test]
899 fn a_test_main_is_seen_in_a_file_nothing_could_parse() {
900 assert!(declares_test_main_textually(
905 "package p\n\nfunc TestMain(m *testing.M) {\n\tos.Exit(m.Run())\n}\n"
906 ));
907 assert!(declares_test_main_textually(
909 "package p\n\nfunc TestMain( m *testing.M ) {}\n"
910 ));
911 assert!(declares_test_main_textually(
912 "package p\n\nfunc TestMain(m *testing.M) { broken( }\n"
913 ));
914
915 assert!(!declares_test_main_textually(
918 "package p\n\nfunc TestMainHelper(t *testing.T) {}\n"
919 ));
920 assert!(!declares_test_main_textually(
922 "package p\n\nfunc TestMain(t *testing.T) {}\n"
923 ));
924 assert!(!declares_test_main_textually(
925 "package p\n\n// func TestMain\n"
926 ));
927 assert!(!declares_test_main_textually("package p\n"));
928 }
929}