1mod context;
2mod file_cache;
3mod hash;
4mod header_dependency;
5mod log;
6mod options;
7
8use self::{
9 context::RunContext,
10 hash::{calculate_content_hash, calculate_timestamp_hash},
11 header_dependency::{exclude_show_includes, read_header_dependencies},
12 log::{debug, profile},
13};
14use crate::{
15 build_graph::{BuildGraph, BuildGraphError},
16 compile::compile_dynamic,
17 context::Context,
18 error::BuildError,
19 hash_type::HashType,
20 infrastructure::{Console, Metadata},
21 ir::{Build, Config, Pool, Rule},
22 parse::parse_dynamic,
23};
24use alloc::sync::Arc;
25use async_recursion::async_recursion;
26use futures::future::{FutureExt, try_join_all};
27use itertools::Itertools;
28pub use options::RunOptions;
29use std::{collections::HashMap, path::Path, process::Output};
30use tokio::{join, spawn, sync::MutexGuard, time::Instant, try_join};
31
32pub async fn run(
34 context: &Arc<Context>,
35 config: Arc<Config>,
36 outputs: &[String],
37 options: RunOptions,
38) -> Result<(), BuildError> {
39 let mut graph = BuildGraph::new(config.outputs());
40 let mut header_dependencies = HashMap::new();
41
42 for build in config
43 .outputs()
44 .values()
45 .filter(|build| build.rule().is_some())
46 .unique_by(|build| build.id())
47 {
48 let dependencies = context.database().get_header_dependencies(build.id())?;
49
50 graph.add_header_dependencies(&build.outputs()[0], &dependencies);
51 header_dependencies.insert(build.id(), dependencies);
52 }
53
54 let context = Arc::new(RunContext::new(
55 context.clone(),
56 config,
57 graph,
58 header_dependencies,
59 options,
60 ));
61
62 context
63 .build_graph()
64 .lock()
65 .await
66 .validate()
67 .map_err(|error| map_build_graph_error(&context, &error))?;
68
69 try_join_all(
70 if outputs.is_empty() {
71 context
72 .config()
73 .default_outputs()
74 .iter()
75 .map(|output| {
76 context
77 .config()
78 .outputs()
79 .get(output.as_ref())
80 .ok_or_else(|| BuildError::DefaultOutputNotFound(output.clone()))
81 })
82 .collect::<Result<Vec<_>, _>>()?
83 } else {
84 outputs
85 .iter()
86 .map(|output| {
87 context
88 .config()
89 .outputs()
90 .get(output.as_str())
91 .ok_or_else(|| BuildError::OutputNotFound(output.clone()))
92 })
93 .collect::<Result<Vec<_>, _>>()?
94 }
95 .into_iter()
96 .map(|build| run_build(context.clone(), build)),
97 )
98 .await?;
99
100 Ok(())
101}
102
103#[async_recursion]
104async fn run_build(context: Arc<RunContext>, build: &Arc<Build>) -> Result<(), BuildError> {
105 let future = context
107 .build_futures()
108 .entry_async(build.id())
109 .await
110 .or_insert_with(|| spawn_build(context.clone(), build.clone()).boxed().shared())
111 .get()
112 .clone();
113
114 future.await
115}
116
117async fn spawn_build(context: Arc<RunContext>, build: Arc<Build>) -> Result<(), BuildError> {
118 spawn(async move {
119 let (_, output_metadata) = try_join!(
120 try_join_all(
121 build
122 .inputs()
123 .iter()
124 .chain(build.order_only_inputs())
125 .map(|input| build_input(context.clone(), input)),
126 ),
127 get_output_metadata(&context, &build),
128 )?;
129
130 let dynamic_config = if let Some(dynamic_module) = build.dynamic_module() {
132 let config = compile_dynamic(&parse_dynamic(
133 &context
134 .build()
135 .file_system()
136 .read_file_to_string(dynamic_module.as_ref().as_ref())
137 .await?,
138 )?)?;
139
140 context
141 .build_graph()
142 .lock()
143 .await
144 .validate_dynamic(&config)
145 .map_err(|error| map_build_graph_error(&context, &error))?;
146
147 Some(config)
148 } else {
149 None
150 };
151
152 let dynamic_inputs = if let Some(config) = &dynamic_config {
153 build
154 .outputs()
155 .iter()
156 .chain(build.implicit_outputs())
157 .find_map(|output| config.outputs().get(output.as_ref()))
158 .map(|build| build.inputs())
159 .ok_or_else(|| BuildError::DynamicDependencyNotFound(build.clone()))?
160 } else {
161 &[]
162 };
163
164 try_join_all(
165 dynamic_inputs
166 .iter()
167 .map(|input| build_input(context.clone(), input)),
168 )
169 .await?;
170
171 if build.rule().is_none() {
172 invalidate_outputs(&context, &build).await;
175 }
176
177 let dependencies = context.header_dependencies(build.id());
178
179 try_join_all(
180 dependencies
181 .iter()
182 .filter_map(|dependency| context.config().outputs().get(dependency.as_str()))
183 .map(|build| run_build(context.clone(), build)),
184 )
185 .await?;
186
187 let header_dependencies =
188 filter_existing_header_dependencies(&context, dependencies).await?;
189
190 let (phony_inputs, file_inputs) =
191 classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
192 let mut timestamp_hash =
193 calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
194
195 if let Some(metadata) = &output_metadata
196 && Some(timestamp_hash)
197 == context
198 .build()
199 .database()
200 .get_hash(HashType::Timestamp, build.id())?
201 {
202 cache_output_metadata(&context, &build, metadata).await;
203
204 return Ok(());
205 }
206
207 let mut content_hash =
208 calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
209
210 if output_metadata.is_some()
211 && Some(content_hash)
212 == context
213 .build()
214 .database()
215 .get_hash(HashType::Content, build.id())?
216 {
217 return Ok(());
218 } else if let Some(rule) = build.rule() {
219 try_join_all(
220 build
221 .outputs()
222 .iter()
223 .chain(build.implicit_outputs())
224 .map(|path| prepare_directory(&context, path.as_ref())),
225 )
226 .await?;
227
228 let output = run_rule(&context, rule).await;
229
230 invalidate_outputs(&context, &build).await;
231
232 let new_header_dependencies =
233 read_header_dependencies(&context, rule, &output?).await?;
234
235 context
236 .build()
237 .database()
238 .set_header_dependencies(build.id(), &new_header_dependencies)?;
239
240 for output in build.outputs() {
241 context.build().database().set_output(output)?;
242
243 if let Some(source) = context.config().source_map().get(output) {
244 context.build().database().set_source(output, source)?;
245 }
246 }
247
248 if header_dependencies != new_header_dependencies {
249 let header_dependencies =
250 filter_existing_header_dependencies(&context, &new_header_dependencies).await?;
251 let (phony_inputs, file_inputs) =
252 classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
253
254 timestamp_hash =
255 calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
256 content_hash =
257 calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
258 }
259 }
260
261 context
262 .build()
263 .database()
264 .set_hash(HashType::Timestamp, build.id(), timestamp_hash)?;
265 context
266 .build()
267 .database()
268 .set_hash(HashType::Content, build.id(), content_hash)?;
269
270 Ok(())
271 })
272 .await?
273}
274
275async fn build_input(context: Arc<RunContext>, input: &str) -> Result<(), BuildError> {
276 if let Some(build) = context.config().outputs().get(input) {
277 run_build(context.clone(), build).await
278 } else {
279 check_file_existence(&context, input).await
280 }
281}
282
283async fn get_output_metadata(
284 context: &RunContext,
285 build: &Build,
286) -> Result<Option<Vec<Metadata>>, BuildError> {
287 Ok(try_join_all(
288 build
289 .outputs()
290 .iter()
291 .chain(build.implicit_outputs())
292 .map(|path| {
293 context
294 .build()
295 .file_system()
296 .metadata(path.as_ref().as_ref())
297 }),
298 )
299 .await?
300 .into_iter()
301 .collect())
302}
303
304async fn cache_output_metadata(context: &RunContext, build: &Build, metadata: &[Metadata]) {
305 if build.rule().is_none() {
307 return;
308 }
309
310 for (path, &metadata) in build
311 .outputs()
312 .iter()
313 .chain(build.implicit_outputs())
314 .zip(metadata)
315 {
316 context
317 .file_cache()
318 .set_metadata(path.as_ref().as_ref(), metadata)
319 .await;
320 }
321}
322
323async fn filter_existing_header_dependencies<'a>(
324 context: &RunContext,
325 dependencies: &'a [String],
326) -> Result<Vec<&'a str>, BuildError> {
327 let mut existing_dependencies = vec![];
328
329 for dependency in dependencies {
330 if context.file_cache().exists(dependency.as_ref()).await? {
331 existing_dependencies.push(dependency.as_str());
332 }
333 }
334
335 Ok(existing_dependencies)
336}
337
338async fn check_file_existence(context: &RunContext, path: &str) -> Result<(), BuildError> {
339 if !context.file_cache().exists(path.as_ref()).await? {
340 return Err(BuildError::FileNotFound(
341 context
342 .build()
343 .database()
344 .get_source(path)?
345 .unwrap_or_else(|| path.into()),
346 ));
347 }
348
349 Ok(())
350}
351
352async fn prepare_directory(context: &RunContext, path: impl AsRef<Path>) -> Result<(), BuildError> {
353 if let Some(directory) = path.as_ref().parent() {
354 context
355 .build()
356 .file_system()
357 .create_directory(directory)
358 .await?;
359 }
360
361 Ok(())
362}
363
364async fn invalidate_outputs(context: &RunContext, build: &Build) {
365 for output in build.outputs().iter().chain(build.implicit_outputs()) {
366 context
367 .file_cache()
368 .invalidate(output.as_ref().as_ref())
369 .await;
370 }
371}
372
373fn classify_inputs<'a>(
374 context: &'a RunContext,
375 build: &'a Build,
376 dynamic_inputs: &'a [Arc<str>],
377 header_dependencies: &'a [&'a str],
378) -> (Vec<&'a str>, Vec<&'a str>) {
379 let (phony_inputs, file_inputs) = build
380 .inputs()
381 .iter()
382 .chain(dynamic_inputs)
383 .map(AsRef::as_ref)
384 .unique()
385 .partition::<Vec<_>, _>(|&input| {
386 context
387 .config()
388 .outputs()
389 .get(input)
390 .map_or_default(|build| build.rule().is_none())
391 });
392
393 (
394 phony_inputs,
395 file_inputs
396 .into_iter()
397 .chain(header_dependencies.iter().copied())
398 .unique()
399 .collect(),
400 )
401}
402
403async fn run_rule(context: &RunContext, rule: &Rule) -> Result<Output, BuildError> {
404 let ((output, duration), mut console) = if rule.pool() == Some(&Pool::Console) {
405 let mut console = write_description(context, rule).await?;
406
407 console.flush().await?;
408
409 let time = Instant::now();
410 let status = context
411 .build()
412 .command_runner()
413 .run_with_console(rule.command())
414 .await?;
415
416 (
417 (
418 Output {
419 status,
420 stdout: vec![],
421 stderr: vec![],
422 },
423 Instant::now() - time,
424 ),
425 console,
426 )
427 } else {
428 let permit = context.pool(rule.pool()).await?;
429 let (output, console) = join!(
430 async {
431 let time = Instant::now();
432 let output = context.build().command_runner().run(rule.command()).await?;
433
434 drop(permit);
435
436 Ok::<_, BuildError>((output, Instant::now() - time))
437 },
438 write_description(context, rule)
439 );
440
441 (output?, console?)
442 };
443
444 profile!(context, console, "duration: {} ms", duration.as_millis());
445
446 console
447 .write_stdout(&exclude_show_includes(rule, &output.stdout))
448 .await?;
449 console.write_stderr(&output.stderr).await?;
450
451 if !output.status.success() {
452 debug!(
453 context,
454 console,
455 "exit status: {}",
456 output
457 .status
458 .code()
459 .map(|code| code.to_string())
460 .unwrap_or_else(|| "-".into())
461 );
462
463 return Err(BuildError::Build);
464 }
465
466 Ok(output)
467}
468
469async fn write_description<'a>(
470 context: &'a RunContext,
471 rule: &Rule,
472) -> Result<MutexGuard<'a, dyn Console + Send + Sync>, BuildError> {
473 let mut console = context.build().console().lock().await;
474
475 if let Some(description) = rule.description() {
476 console.write_stderr(description.as_bytes()).await?;
477 console.write_stderr(b"\n").await?;
478 }
479
480 debug!(context, console, "command: {}", rule.command());
481
482 Ok(console)
483}
484
485fn map_build_graph_error(context: &RunContext, error: &BuildGraphError) -> BuildError {
486 match error {
487 BuildGraphError::CircularDependency(outputs) => {
488 match outputs
489 .iter()
490 .map(|output| {
491 Ok(context
492 .build()
493 .database()
494 .get_source(output)?
495 .map(|string| string.into())
496 .unwrap_or_else(|| output.clone()))
497 })
498 .collect::<Result<Vec<_>, BuildError>>()
499 {
500 Ok(outputs) => {
501 BuildGraphError::CircularDependency(outputs.into_iter().dedup().collect())
502 .into()
503 }
504 Err(error) => error,
505 }
506 }
507 BuildGraphError::OutputNotFound(_) => error.clone().into(),
508 }
509}
510
511#[cfg(test)]
512mod tests {
513 use super::*;
514 use crate::{
515 infrastructure::{FakeCommandRunner, FakeConsole, FakeDatabase, FakeFileSystem, FileError},
516 ir::HeaderDependency,
517 };
518 use core::{num::NonZeroUsize, pin::pin};
519 use futures::poll;
520 use pretty_assertions::{assert_eq, assert_ne};
521 use regex::Regex;
522 #[cfg(unix)]
523 use std::os::unix::process::ExitStatusExt;
524 #[cfg(windows)]
525 use std::os::windows::process::ExitStatusExt;
526 use std::{collections::HashMap, process::ExitStatus};
527 use tokio::{sync::Mutex, task::yield_now};
528
529 const DEFAULT_OPTIONS: RunOptions = RunOptions {
530 debug: false,
531 profile: false,
532 };
533 const POLL_COUNT: usize = 8;
534
535 fn create_context(
536 command_runner: &FakeCommandRunner,
537 console: &FakeConsole,
538 file_system: &FakeFileSystem,
539 ) -> Arc<Context> {
540 Context::new(
541 command_runner.clone(),
542 Mutex::new(console.clone()).into(),
543 FakeDatabase::default(),
544 file_system.clone(),
545 )
546 .into()
547 }
548
549 fn create_outputs(builds: Vec<Build>) -> HashMap<Arc<str>, Arc<Build>> {
550 builds
551 .into_iter()
552 .map(Arc::new)
553 .flat_map(|build| {
554 build
555 .outputs()
556 .iter()
557 .chain(build.implicit_outputs())
558 .map(|output| (output.clone(), build.clone()))
559 .collect::<Vec<_>>()
560 })
561 .collect()
562 }
563
564 fn create_simple_config(builds: Vec<Build>, default_outputs: &[&str]) -> Arc<Config> {
565 create_pool_config(builds, default_outputs, &[])
566 }
567
568 fn create_pool_config(
569 builds: Vec<Build>,
570 default_outputs: &[&str],
571 pools: &[(&str, usize)],
572 ) -> Arc<Config> {
573 Config::new(
574 create_outputs(builds),
575 default_outputs
576 .iter()
577 .map(|&output| output.into())
578 .collect(),
579 Default::default(),
580 pools
581 .iter()
582 .map(|&(name, depth)| (name.into(), NonZeroUsize::new(depth).unwrap()))
583 .collect(),
584 None,
585 )
586 .into()
587 }
588
589 fn explicit_build(outputs: Vec<Arc<str>>, rule: Rule, inputs: Vec<Arc<str>>) -> Build {
590 Build::new(outputs, vec![], rule.into(), inputs, vec![], None)
591 }
592
593 fn failed_output() -> Output {
594 Output {
595 status: ExitStatus::from_raw(cfg_select! {
596 unix => 1 << 8,
597 windows => 1,
598 }),
599 stdout: vec![],
600 stderr: vec![],
601 }
602 }
603
604 fn limited_pool(name: &str) -> Option<Pool> {
605 Some(Pool::Limited(name.into()))
606 }
607
608 fn pool_build(output: &str, pool: Option<Pool>) -> Build {
609 explicit_build(
610 vec![output.into()],
611 Rule::new(format!("touch {output}"), None).with_pool(pool),
612 vec![],
613 )
614 }
615
616 fn count_metadata_requests(file_system: &FakeFileSystem, path: &str) -> usize {
617 file_system
618 .metadata_requests()
619 .iter()
620 .filter(|request| request.as_path() == Path::new(path))
621 .count()
622 }
623
624 fn create_run_context(
625 command_runner: &FakeCommandRunner,
626 console: &FakeConsole,
627 pools: &[(&str, usize)],
628 ) -> RunContext {
629 RunContext::new(
630 create_context(command_runner, console, &Default::default()),
631 create_pool_config(vec![], &[], pools),
632 BuildGraph::new(&Default::default()),
633 Default::default(),
634 DEFAULT_OPTIONS,
635 )
636 }
637
638 fn create_build_run_context(
639 command_runner: &FakeCommandRunner,
640 file_system: &FakeFileSystem,
641 builds: Vec<Build>,
642 ) -> Arc<RunContext> {
643 RunContext::new(
644 create_context(command_runner, &Default::default(), file_system),
645 create_simple_config(builds, &[]),
646 BuildGraph::new(&Default::default()),
647 Default::default(),
648 DEFAULT_OPTIONS,
649 )
650 .into()
651 }
652
653 #[tokio::test]
654 async fn build_nothing() {
655 let command_runner = FakeCommandRunner::default();
656
657 run(
658 &create_context(&command_runner, &Default::default(), &Default::default()),
659 create_simple_config(vec![], &[]),
660 &[],
661 DEFAULT_OPTIONS,
662 )
663 .await
664 .unwrap();
665
666 assert_eq!(command_runner.commands(), Vec::<String>::new());
667 }
668
669 #[tokio::test]
670 async fn build_default_output() {
671 let command_runner = FakeCommandRunner::default();
672
673 run(
674 &create_context(&command_runner, &Default::default(), &Default::default()),
675 create_simple_config(
676 vec![
677 explicit_build(
678 vec!["foo".into()],
679 Rule::new("touch foo".into(), None),
680 vec![],
681 ),
682 explicit_build(
683 vec!["bar".into()],
684 Rule::new("touch bar".into(), None),
685 vec![],
686 ),
687 ],
688 &["foo"],
689 ),
690 &[],
691 DEFAULT_OPTIONS,
692 )
693 .await
694 .unwrap();
695
696 assert_eq!(command_runner.commands(), ["touch foo"]);
697 }
698
699 #[tokio::test]
700 async fn build_specified_output() {
701 let command_runner = FakeCommandRunner::default();
702
703 run(
704 &create_context(&command_runner, &Default::default(), &Default::default()),
705 create_simple_config(
706 vec![
707 explicit_build(
708 vec!["foo".into()],
709 Rule::new("touch foo".into(), None),
710 vec![],
711 ),
712 explicit_build(
713 vec!["bar".into()],
714 Rule::new("touch bar".into(), None),
715 vec![],
716 ),
717 ],
718 &["foo"],
719 ),
720 &["bar".into()],
721 DEFAULT_OPTIONS,
722 )
723 .await
724 .unwrap();
725
726 assert_eq!(command_runner.commands(), ["touch bar"]);
727 }
728
729 #[tokio::test]
730 async fn build_duplicate_outputs() {
731 let command_runner = FakeCommandRunner::default();
732
733 run(
734 &create_context(&command_runner, &Default::default(), &Default::default()),
735 create_simple_config(
736 vec![explicit_build(
737 vec!["foo".into()],
738 Rule::new("touch foo".into(), None),
739 vec![],
740 )],
741 &[],
742 ),
743 &["foo".into(), "foo".into()],
744 DEFAULT_OPTIONS,
745 )
746 .await
747 .unwrap();
748
749 assert_eq!(command_runner.commands(), ["touch foo"]);
750 }
751
752 #[tokio::test]
753 async fn fail_with_unknown_default_output() {
754 assert_eq!(
755 run(
756 &create_context(
757 &Default::default(),
758 &Default::default(),
759 &Default::default()
760 ),
761 create_simple_config(vec![], &["foo"]),
762 &[],
763 DEFAULT_OPTIONS,
764 )
765 .await,
766 Err(BuildError::DefaultOutputNotFound("foo".into()))
767 );
768 }
769
770 #[tokio::test]
771 async fn fail_with_unknown_output() {
772 assert_eq!(
773 run(
774 &create_context(
775 &Default::default(),
776 &Default::default(),
777 &Default::default()
778 ),
779 create_simple_config(vec![], &[]),
780 &["foo".into()],
781 DEFAULT_OPTIONS,
782 )
783 .await,
784 Err(BuildError::OutputNotFound("foo".into()))
785 );
786 }
787
788 #[tokio::test]
789 async fn fail_with_unknown_output_after_known_output() {
790 let command_runner = FakeCommandRunner::default();
791
792 assert_eq!(
793 run(
794 &create_context(&command_runner, &Default::default(), &Default::default()),
795 create_simple_config(
796 vec![explicit_build(
797 vec!["foo".into()],
798 Rule::new("touch foo".into(), None),
799 vec![],
800 )],
801 &[],
802 ),
803 &["foo".into(), "bar".into()],
804 DEFAULT_OPTIONS,
805 )
806 .await,
807 Err(BuildError::OutputNotFound("bar".into()))
808 );
809
810 yield_now().await;
812
813 assert_eq!(command_runner.commands(), Vec::<String>::new());
814 }
815
816 #[tokio::test]
817 async fn build_shared_input_once() {
818 let command_runner = FakeCommandRunner::default();
819 let file_system = FakeFileSystem::default();
820
821 file_system.write_file("bar", "");
822 file_system.write_file("baz", "");
823
824 run(
825 &create_context(&command_runner, &Default::default(), &file_system),
826 create_simple_config(
827 vec![
828 explicit_build(
829 vec!["foo".into()],
830 Rule::new("touch foo".into(), None),
831 vec!["bar".into(), "baz".into()],
832 ),
833 explicit_build(
834 vec!["bar".into()],
835 Rule::new("touch bar".into(), None),
836 vec!["baz".into()],
837 ),
838 explicit_build(
839 vec!["baz".into()],
840 Rule::new("touch baz".into(), None),
841 vec![],
842 ),
843 ],
844 &["foo"],
845 ),
846 &[],
847 DEFAULT_OPTIONS,
848 )
849 .await
850 .unwrap();
851
852 assert_eq!(
853 command_runner.commands(),
854 ["touch baz", "touch bar", "touch foo"]
855 );
856 }
857
858 #[tokio::test]
859 async fn build_order_only_input() {
860 let command_runner = FakeCommandRunner::default();
861
862 run(
863 &create_context(&command_runner, &Default::default(), &Default::default()),
864 create_simple_config(
865 vec![
866 Build::new(
867 vec!["foo".into()],
868 vec![],
869 Rule::new("touch foo".into(), None).into(),
870 vec![],
871 vec!["bar".into()],
872 None,
873 ),
874 explicit_build(
875 vec!["bar".into()],
876 Rule::new("touch bar".into(), None),
877 vec![],
878 ),
879 ],
880 &["foo"],
881 ),
882 &[],
883 DEFAULT_OPTIONS,
884 )
885 .await
886 .unwrap();
887
888 assert_eq!(command_runner.commands(), ["touch bar", "touch foo"]);
889 }
890
891 #[tokio::test]
892 async fn build_multiple_outputs_of_build() {
893 let command_runner = FakeCommandRunner::default();
894 let file_system = FakeFileSystem::default();
895
896 file_system.write_file("foo", "");
897 file_system.write_file("bar", "");
898
899 run(
900 &create_context(&command_runner, &Default::default(), &file_system),
901 create_simple_config(
902 vec![
903 explicit_build(
904 vec!["foo".into(), "bar".into()],
905 Rule::new("touch foo bar".into(), None),
906 vec![],
907 ),
908 explicit_build(
909 vec!["baz".into()],
910 Rule::new("cat foo bar > baz".into(), None),
911 vec!["foo".into(), "bar".into()],
912 ),
913 ],
914 &["baz"],
915 ),
916 &[],
917 DEFAULT_OPTIONS,
918 )
919 .await
920 .unwrap();
921
922 assert_eq!(
923 command_runner.commands(),
924 ["touch foo bar", "cat foo bar > baz"]
925 );
926 }
927
928 #[tokio::test]
929 async fn fail_with_missing_input() {
930 let command_runner = FakeCommandRunner::default();
931
932 assert_eq!(
933 run(
934 &create_context(&command_runner, &Default::default(), &Default::default()),
935 create_simple_config(
936 vec![explicit_build(
937 vec!["foo".into()],
938 Rule::new("cp bar foo".into(), None),
939 vec!["bar".into()],
940 )],
941 &["foo"],
942 ),
943 &[],
944 DEFAULT_OPTIONS,
945 )
946 .await,
947 Err(BuildError::FileNotFound("bar".into()))
948 );
949 assert_eq!(command_runner.commands(), Vec::<String>::new());
950 }
951
952 #[tokio::test]
953 async fn fail_with_missing_order_only_input() {
954 assert_eq!(
955 run(
956 &create_context(
957 &Default::default(),
958 &Default::default(),
959 &Default::default()
960 ),
961 create_simple_config(
962 vec![Build::new(
963 vec!["foo".into()],
964 vec![],
965 Rule::new("touch foo".into(), None).into(),
966 vec![],
967 vec!["bar".into()],
968 None,
969 )],
970 &["foo"],
971 ),
972 &[],
973 DEFAULT_OPTIONS,
974 )
975 .await,
976 Err(BuildError::FileNotFound("bar".into()))
977 );
978 }
979
980 #[tokio::test]
981 async fn report_source_of_missing_input() {
982 let context = create_context(
983 &Default::default(),
984 &Default::default(),
985 &Default::default(),
986 );
987
988 context.database().set_source("foo.o", "foo.c").unwrap();
989
990 assert_eq!(
991 run(
992 &context,
993 create_simple_config(
994 vec![explicit_build(
995 vec!["foo".into()],
996 Rule::new("cc foo.o".into(), None),
997 vec!["foo.o".into()],
998 )],
999 &["foo"],
1000 ),
1001 &[],
1002 DEFAULT_OPTIONS,
1003 )
1004 .await,
1005 Err(BuildError::FileNotFound("foo.c".into()))
1006 );
1007 }
1008
1009 #[tokio::test]
1010 async fn detect_circular_dependency() {
1011 assert_eq!(
1012 run(
1013 &create_context(
1014 &Default::default(),
1015 &Default::default(),
1016 &Default::default()
1017 ),
1018 create_simple_config(
1019 vec![explicit_build(
1020 vec!["foo".into()],
1021 Rule::new("cp foo foo".into(), None),
1022 vec!["foo".into()],
1023 )],
1024 &["foo"],
1025 ),
1026 &[],
1027 DEFAULT_OPTIONS,
1028 )
1029 .await,
1030 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1031 vec!["foo".into()]
1032 )))
1033 );
1034 }
1035
1036 #[tokio::test]
1037 async fn report_sources_in_circular_dependency() {
1038 let context = create_context(
1039 &Default::default(),
1040 &Default::default(),
1041 &Default::default(),
1042 );
1043
1044 context.database().set_source("foo", "baz").unwrap();
1045 context.database().set_source("bar", "baz").unwrap();
1046
1047 assert_eq!(
1048 run(
1049 &context,
1050 create_simple_config(
1051 vec![
1052 explicit_build(
1053 vec!["foo".into()],
1054 Rule::new("cp bar foo".into(), None),
1055 vec!["bar".into()],
1056 ),
1057 explicit_build(
1058 vec!["bar".into()],
1059 Rule::new("cp foo bar".into(), None),
1060 vec!["foo".into()],
1061 ),
1062 ],
1063 &["foo"],
1064 ),
1065 &[],
1066 DEFAULT_OPTIONS,
1067 )
1068 .await,
1069 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1070 vec!["baz".into()]
1071 )))
1072 );
1073 }
1074
1075 #[tokio::test]
1076 async fn prepare_output_directory() {
1077 let context = create_context(
1078 &Default::default(),
1079 &Default::default(),
1080 &Default::default(),
1081 );
1082
1083 run(
1084 &context,
1085 create_simple_config(
1086 vec![explicit_build(
1087 vec!["foo/bar".into()],
1088 Rule::new("touch foo/bar".into(), None),
1089 vec![],
1090 )],
1091 &["foo/bar"],
1092 ),
1093 &[],
1094 DEFAULT_OPTIONS,
1095 )
1096 .await
1097 .unwrap();
1098
1099 assert!(context.file_system().exists("foo".as_ref()).await.unwrap());
1100 }
1101
1102 #[tokio::test]
1103 async fn record_output_with_source() {
1104 let file_system = FakeFileSystem::default();
1105 let context = create_context(&Default::default(), &Default::default(), &file_system);
1106
1107 file_system.write_file("foo.c", "");
1108
1109 run(
1110 &context,
1111 Config::new(
1112 create_outputs(vec![explicit_build(
1113 vec!["foo.o".into()],
1114 Rule::new("cc foo.c".into(), None),
1115 vec!["foo.c".into()],
1116 )]),
1117 ["foo.o".into()].into_iter().collect(),
1118 [("foo.o".into(), "foo.c".into())].into_iter().collect(),
1119 Default::default(),
1120 None,
1121 )
1122 .into(),
1123 &[],
1124 DEFAULT_OPTIONS,
1125 )
1126 .await
1127 .unwrap();
1128
1129 assert_eq!(context.database().get_outputs().unwrap(), ["foo.o"]);
1130 assert_eq!(
1131 context.database().get_source("foo.o").unwrap(),
1132 Some("foo.c".into())
1133 );
1134 }
1135
1136 #[tokio::test]
1137 async fn skip_up_to_date_build() {
1138 let command_runner = FakeCommandRunner::default();
1139 let file_system = FakeFileSystem::default();
1140 let context = create_context(&command_runner, &Default::default(), &file_system);
1141 let config = create_simple_config(
1142 vec![explicit_build(
1143 vec!["foo".into()],
1144 Rule::new("cp bar foo".into(), None),
1145 vec!["bar".into()],
1146 )],
1147 &["foo"],
1148 );
1149
1150 file_system.write_file("foo", "");
1151 file_system.write_file("bar", "");
1152
1153 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1154 .await
1155 .unwrap();
1156 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1157
1158 assert_eq!(command_runner.commands(), ["cp bar foo"]);
1159 }
1160
1161 #[tokio::test]
1162 async fn skip_build_on_timestamp_update_of_input() {
1163 let command_runner = FakeCommandRunner::default();
1164 let file_system = FakeFileSystem::default();
1165 let context = create_context(&command_runner, &Default::default(), &file_system);
1166 let config = create_simple_config(
1167 vec![explicit_build(
1168 vec!["foo".into()],
1169 Rule::new("cp bar foo".into(), None),
1170 vec!["bar".into()],
1171 )],
1172 &["foo"],
1173 );
1174
1175 file_system.write_file("foo", "");
1176 file_system.write_file("bar", "");
1177
1178 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1179 .await
1180 .unwrap();
1181
1182 file_system.write_file("bar", "");
1183
1184 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1185
1186 assert_eq!(command_runner.commands(), ["cp bar foo"]);
1187 }
1188
1189 #[tokio::test]
1190 async fn rebuild_on_content_update_of_input() {
1191 let command_runner = FakeCommandRunner::default();
1192 let file_system = FakeFileSystem::default();
1193 let context = create_context(&command_runner, &Default::default(), &file_system);
1194 let config = create_simple_config(
1195 vec![explicit_build(
1196 vec!["foo".into()],
1197 Rule::new("cp bar foo".into(), None),
1198 vec!["bar".into()],
1199 )],
1200 &["foo"],
1201 );
1202
1203 file_system.write_file("foo", "");
1204 file_system.write_file("bar", "");
1205
1206 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1207 .await
1208 .unwrap();
1209
1210 file_system.write_file("bar", "bar");
1211
1212 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1213
1214 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1215 }
1216
1217 #[tokio::test]
1218 async fn rebuild_on_update_of_phony_input() {
1219 let command_runner = FakeCommandRunner::default();
1220 let file_system = FakeFileSystem::default();
1221 let context = create_context(&command_runner, &Default::default(), &file_system);
1222 let config = create_simple_config(
1223 vec![
1224 explicit_build(
1225 vec!["foo".into()],
1226 Rule::new("cp bar foo".into(), None),
1227 vec!["bar".into()],
1228 ),
1229 Build::new(
1230 vec!["bar".into()],
1231 vec![],
1232 None,
1233 vec!["baz".into()],
1234 vec![],
1235 None,
1236 ),
1237 ],
1238 &["foo"],
1239 );
1240
1241 file_system.write_file("foo", "");
1242 file_system.write_file("baz", "");
1243
1244 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1245 .await
1246 .unwrap();
1247
1248 file_system.write_file("baz", "baz");
1249
1250 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1251
1252 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1253 }
1254
1255 #[tokio::test]
1256 async fn rebuild_missing_output() {
1257 let command_runner = FakeCommandRunner::default();
1258 let file_system = FakeFileSystem::default();
1259 let context = create_context(&command_runner, &Default::default(), &file_system);
1260 let config = create_simple_config(
1261 vec![explicit_build(
1262 vec!["foo".into()],
1263 Rule::new("cp bar foo".into(), None),
1264 vec!["bar".into()],
1265 )],
1266 &["foo"],
1267 );
1268
1269 file_system.write_file("bar", "");
1270
1271 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1272 .await
1273 .unwrap();
1274 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1275
1276 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1277 }
1278
1279 #[tokio::test]
1280 async fn rebuild_missing_implicit_output() {
1281 let command_runner = FakeCommandRunner::default();
1282 let file_system = FakeFileSystem::default();
1283 let context = create_context(&command_runner, &Default::default(), &file_system);
1284 let config = create_simple_config(
1285 vec![Build::new(
1286 vec!["foo".into()],
1287 vec!["baz".into()],
1288 Rule::new("cp bar foo".into(), None).into(),
1289 vec!["bar".into()],
1290 vec![],
1291 None,
1292 )],
1293 &["foo"],
1294 );
1295
1296 file_system.write_file("foo", "");
1297 file_system.write_file("bar", "");
1298
1299 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1300 .await
1301 .unwrap();
1302 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1303
1304 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1305 }
1306
1307 #[tokio::test]
1308 async fn rerun_failed_build() {
1309 let command_runner =
1310 FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1311 let file_system = FakeFileSystem::default();
1312 let context = create_context(&command_runner, &Default::default(), &file_system);
1313 let config = create_simple_config(
1314 vec![explicit_build(
1315 vec!["foo".into()],
1316 Rule::new("exit 1".into(), None),
1317 vec![],
1318 )],
1319 &["foo"],
1320 );
1321
1322 file_system.write_file("foo", "");
1323
1324 assert_eq!(
1325 run(&context, config.clone(), &[], DEFAULT_OPTIONS).await,
1326 Err(BuildError::Build)
1327 );
1328 assert_eq!(
1329 run(&context, config, &[], DEFAULT_OPTIONS).await,
1330 Err(BuildError::Build)
1331 );
1332 assert_eq!(command_runner.commands(), ["exit 1", "exit 1"]);
1333 }
1334
1335 #[tokio::test]
1336 async fn invalidate_file_cache_of_outputs_after_command() {
1337 let command_runner = FakeCommandRunner::default();
1338 let file_system = FakeFileSystem::default();
1339 let context = create_build_run_context(&command_runner, &file_system, vec![]);
1340 let build = Build::new(
1341 vec!["foo".into()],
1342 vec!["bar".into()],
1343 Rule::new("touch foo bar".into(), None).into(),
1344 vec![],
1345 vec![],
1346 None,
1347 )
1348 .into();
1349 let mut future = pin!(run_build(context.clone(), &build));
1350
1351 file_system.write_file("foo", "1");
1352 file_system.write_file("bar", "1");
1353
1354 assert!(poll!(&mut future).is_pending());
1355
1356 yield_now().await;
1358
1359 assert_eq!(command_runner.commands(), ["touch foo bar"]);
1360
1361 let foo_hash = context.file_cache().content_hash("foo".as_ref()).await;
1362 let bar_hash = context.file_cache().content_hash("bar".as_ref()).await;
1363
1364 file_system.write_file("foo", "2");
1365 file_system.write_file("bar", "2");
1366 future.await.unwrap();
1367
1368 assert_ne!(
1369 context.file_cache().content_hash("foo".as_ref()).await,
1370 foo_hash
1371 );
1372 assert_ne!(
1373 context.file_cache().content_hash("bar".as_ref()).await,
1374 bar_hash
1375 );
1376 }
1377
1378 #[tokio::test]
1379 async fn invalidate_file_cache_of_output_after_failed_command() {
1380 let command_runner =
1381 FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1382 let file_system = FakeFileSystem::default();
1383 let context = create_build_run_context(&command_runner, &file_system, vec![]);
1384 let build =
1385 explicit_build(vec!["foo".into()], Rule::new("exit 1".into(), None), vec![]).into();
1386 let mut future = pin!(run_build(context.clone(), &build));
1387
1388 file_system.write_file("foo", "1");
1389
1390 assert!(poll!(&mut future).is_pending());
1391
1392 yield_now().await;
1394
1395 assert_eq!(command_runner.commands(), ["exit 1"]);
1396
1397 let hash = context.file_cache().content_hash("foo".as_ref()).await;
1398
1399 file_system.write_file("foo", "2");
1400
1401 assert_eq!(future.await, Err(BuildError::Build));
1402 assert_ne!(
1403 context.file_cache().content_hash("foo".as_ref()).await,
1404 hash
1405 );
1406 }
1407
1408 #[tokio::test]
1409 async fn invalidate_file_cache_of_phony_output_after_inputs() {
1410 let command_runner = FakeCommandRunner::default();
1411 let file_system = FakeFileSystem::default();
1412 let build = Build::new(
1413 vec!["foo".into()],
1414 vec![],
1415 None,
1416 vec!["bar".into()],
1417 vec![],
1418 None,
1419 );
1420 let context = create_build_run_context(
1421 &command_runner,
1422 &file_system,
1423 vec![
1424 build.clone(),
1425 explicit_build(
1426 vec!["bar".into()],
1427 Rule::new("touch bar".into(), None),
1428 vec![],
1429 ),
1430 ],
1431 );
1432 let build = build.into();
1433 let mut future = pin!(run_build(context.clone(), &build));
1434
1435 file_system.write_file("foo", "1");
1436 file_system.write_file("bar", "");
1437
1438 assert!(poll!(&mut future).is_pending());
1439
1440 yield_now().await;
1442
1443 assert_eq!(command_runner.commands(), ["touch bar"]);
1444
1445 let hash = context.file_cache().content_hash("foo".as_ref()).await;
1446
1447 file_system.write_file("foo", "2");
1448 future.await.unwrap();
1449
1450 assert_ne!(
1451 context.file_cache().content_hash("foo".as_ref()).await,
1452 hash
1453 );
1454 }
1455
1456 #[tokio::test]
1457 async fn keep_file_cache_of_other_file_after_command() {
1458 let command_runner = FakeCommandRunner::default();
1459 let file_system = FakeFileSystem::default();
1460 let context = create_build_run_context(&command_runner, &file_system, vec![]);
1461
1462 file_system.write_file("bar", "1");
1463
1464 let hash = context.file_cache().content_hash("bar".as_ref()).await;
1465
1466 file_system.write_file("bar", "2");
1467 run_build(
1468 context.clone(),
1469 &explicit_build(
1470 vec!["foo".into()],
1471 Rule::new("touch foo".into(), None),
1472 vec![],
1473 )
1474 .into(),
1475 )
1476 .await
1477 .unwrap();
1478
1479 assert_eq!(command_runner.commands(), ["touch foo"]);
1480 assert_eq!(
1481 context.file_cache().content_hash("bar".as_ref()).await,
1482 hash
1483 );
1484 }
1485
1486 #[tokio::test]
1487 async fn query_output_while_building_input() {
1488 let file_system = FakeFileSystem::default();
1489
1490 file_system.write_file("foo", "");
1491 file_system.write_file("bar", "");
1492 file_system.write_file("baz", "");
1493
1494 run(
1495 &create_context(&Default::default(), &Default::default(), &file_system),
1496 create_simple_config(
1497 vec![
1498 explicit_build(
1499 vec!["foo".into()],
1500 Rule::new("cp bar foo".into(), None),
1501 vec!["bar".into()],
1502 ),
1503 explicit_build(
1504 vec!["bar".into()],
1505 Rule::new("cp baz bar".into(), None),
1506 vec!["baz".into()],
1507 ),
1508 ],
1509 &["foo"],
1510 ),
1511 &[],
1512 DEFAULT_OPTIONS,
1513 )
1514 .await
1515 .unwrap();
1516
1517 assert_eq!(file_system.metadata_requests().first(), Some(&"foo".into()));
1518 }
1519
1520 #[tokio::test]
1521 async fn query_output_once_for_up_to_date_build() {
1522 let file_system = FakeFileSystem::default();
1523 let context = create_context(&Default::default(), &Default::default(), &file_system);
1524 let config = create_simple_config(
1525 vec![
1526 explicit_build(
1527 vec!["foo".into()],
1528 Rule::new("cp bar foo".into(), None),
1529 vec!["bar".into()],
1530 ),
1531 explicit_build(
1532 vec!["bar".into()],
1533 Rule::new("cp baz bar".into(), None),
1534 vec!["baz".into()],
1535 ),
1536 ],
1537 &["foo"],
1538 );
1539
1540 file_system.write_file("foo", "");
1541 file_system.write_file("bar", "");
1542 file_system.write_file("baz", "");
1543
1544 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1545 .await
1546 .unwrap();
1547
1548 let count = count_metadata_requests(&file_system, "bar");
1549
1550 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1551
1552 assert_eq!(count_metadata_requests(&file_system, "bar"), count + 1);
1553 }
1554
1555 #[tokio::test]
1556 async fn query_implicit_output_once_for_up_to_date_build() {
1557 let file_system = FakeFileSystem::default();
1558 let context = create_context(&Default::default(), &Default::default(), &file_system);
1559 let config = create_simple_config(
1560 vec![
1561 explicit_build(
1562 vec!["foo".into()],
1563 Rule::new("cp qux foo".into(), None),
1564 vec!["qux".into()],
1565 ),
1566 Build::new(
1567 vec!["bar".into()],
1568 vec!["qux".into()],
1569 Rule::new("cp baz bar".into(), None).into(),
1570 vec!["baz".into()],
1571 vec![],
1572 None,
1573 ),
1574 ],
1575 &["foo"],
1576 );
1577
1578 file_system.write_file("foo", "");
1579 file_system.write_file("bar", "");
1580 file_system.write_file("baz", "");
1581 file_system.write_file("qux", "");
1582
1583 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1584 .await
1585 .unwrap();
1586
1587 let count = count_metadata_requests(&file_system, "qux");
1588
1589 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1590
1591 assert_eq!(count_metadata_requests(&file_system, "qux"), count + 1);
1592 }
1593
1594 #[tokio::test]
1595 async fn query_output_again_on_timestamp_update_of_input() {
1596 let file_system = FakeFileSystem::default();
1597 let context = create_context(&Default::default(), &Default::default(), &file_system);
1598 let config = create_simple_config(
1599 vec![
1600 explicit_build(
1601 vec!["foo".into()],
1602 Rule::new("cp bar foo".into(), None),
1603 vec!["bar".into()],
1604 ),
1605 explicit_build(
1606 vec!["bar".into()],
1607 Rule::new("cp baz bar".into(), None),
1608 vec!["baz".into()],
1609 ),
1610 ],
1611 &["foo"],
1612 );
1613
1614 file_system.write_file("foo", "");
1615 file_system.write_file("bar", "");
1616 file_system.write_file("baz", "");
1617
1618 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1619 .await
1620 .unwrap();
1621
1622 file_system.write_file("baz", "");
1623
1624 let count = count_metadata_requests(&file_system, "bar");
1625
1626 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1627
1628 assert_eq!(count_metadata_requests(&file_system, "bar"), count + 2);
1629 }
1630
1631 #[tokio::test]
1632 async fn query_output_again_after_command() {
1633 let file_system = FakeFileSystem::default();
1634
1635 file_system.write_file("foo", "");
1636 file_system.write_file("bar", "");
1637 file_system.write_file("baz", "");
1638
1639 run(
1640 &create_context(&Default::default(), &Default::default(), &file_system),
1641 create_simple_config(
1642 vec![
1643 explicit_build(
1644 vec!["foo".into()],
1645 Rule::new("cp bar foo".into(), None),
1646 vec!["bar".into()],
1647 ),
1648 explicit_build(
1649 vec!["bar".into()],
1650 Rule::new("cp baz bar".into(), None),
1651 vec!["baz".into()],
1652 ),
1653 ],
1654 &["foo"],
1655 ),
1656 &[],
1657 DEFAULT_OPTIONS,
1658 )
1659 .await
1660 .unwrap();
1661
1662 assert_eq!(count_metadata_requests(&file_system, "bar"), 2);
1663 }
1664
1665 #[tokio::test]
1666 async fn query_output_again_for_up_to_date_phony_build() {
1667 let file_system = FakeFileSystem::default();
1668 let context = create_context(&Default::default(), &Default::default(), &file_system);
1669 let config = create_simple_config(
1670 vec![
1671 explicit_build(
1672 vec!["foo.o".into()],
1673 Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
1674 HeaderDependency::Make {
1675 path: "foo.d".into(),
1676 },
1677 )),
1678 vec!["foo.c".into()],
1679 ),
1680 Build::new(
1681 vec!["foo.h".into()],
1682 vec![],
1683 None,
1684 vec!["foo.h.in".into()],
1685 vec![],
1686 None,
1687 ),
1688 ],
1689 &["foo.o", "foo.h"],
1690 );
1691
1692 file_system.write_file("foo.o", "");
1693 file_system.write_file("foo.c", "");
1694 file_system.write_file("foo.h", "");
1695 file_system.write_file("foo.h.in", "");
1696 file_system.write_file("foo.d", "foo.o: foo.h\n");
1697
1698 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1699 .await
1700 .unwrap();
1701
1702 let count = count_metadata_requests(&file_system, "foo.h");
1703
1704 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1705
1706 assert_eq!(count_metadata_requests(&file_system, "foo.h"), count + 2);
1707 }
1708
1709 #[tokio::test]
1710 async fn fail_with_output_metadata_error() {
1711 let command_runner = FakeCommandRunner::default();
1712 let file_system = FakeFileSystem::default();
1713
1714 file_system.write_file("bar", "");
1715 file_system.fail_metadata("foo");
1716
1717 assert_eq!(
1718 run(
1719 &create_context(&command_runner, &Default::default(), &file_system),
1720 create_simple_config(
1721 vec![explicit_build(
1722 vec!["foo".into()],
1723 Rule::new("cp bar foo".into(), None),
1724 vec!["bar".into()],
1725 )],
1726 &["foo"],
1727 ),
1728 &[],
1729 DEFAULT_OPTIONS,
1730 )
1731 .await,
1732 Err(BuildError::File(FileError::new("metadata failure")))
1733 );
1734 assert_eq!(command_runner.commands(), Vec::<String>::new());
1735 }
1736
1737 #[tokio::test]
1738 async fn write_command_output() {
1739 let console = FakeConsole::default();
1740
1741 run(
1742 &create_context(
1743 &FakeCommandRunner::new(
1744 [(
1745 "touch foo".into(),
1746 Output {
1747 status: ExitStatus::default(),
1748 stdout: b"bar\n".into(),
1749 stderr: b"baz\n".into(),
1750 },
1751 )]
1752 .into_iter()
1753 .collect(),
1754 ),
1755 &console,
1756 &Default::default(),
1757 ),
1758 create_simple_config(
1759 vec![explicit_build(
1760 vec!["foo".into()],
1761 Rule::new("touch foo".into(), Some("build foo".into())),
1762 vec![],
1763 )],
1764 &["foo"],
1765 ),
1766 &[],
1767 DEFAULT_OPTIONS,
1768 )
1769 .await
1770 .unwrap();
1771
1772 assert_eq!(console.stdout(), "bar\n");
1773 assert_eq!(console.stderr(), "build foo\nbaz\n");
1774 }
1775
1776 #[tokio::test]
1777 async fn write_debug_log_of_failed_build() {
1778 let console = FakeConsole::default();
1779
1780 assert_eq!(
1781 run(
1782 &create_context(
1783 &FakeCommandRunner::new(
1784 [("exit 1".into(), failed_output())].into_iter().collect(),
1785 ),
1786 &console,
1787 &Default::default(),
1788 ),
1789 create_simple_config(
1790 vec![explicit_build(
1791 vec!["foo".into()],
1792 Rule::new("exit 1".into(), None),
1793 vec![],
1794 )],
1795 &["foo"],
1796 ),
1797 &[],
1798 RunOptions {
1799 debug: true,
1800 profile: false,
1801 },
1802 )
1803 .await,
1804 Err(BuildError::Build)
1805 );
1806 assert_eq!(
1807 console.stderr(),
1808 "turtle: command: exit 1\nturtle: exit status: 1\n"
1809 );
1810 }
1811
1812 #[tokio::test]
1813 async fn write_profile_log() {
1814 let console = FakeConsole::default();
1815
1816 run(
1817 &create_context(&Default::default(), &console, &Default::default()),
1818 create_simple_config(
1819 vec![explicit_build(
1820 vec!["foo".into()],
1821 Rule::new("touch foo".into(), None),
1822 vec![],
1823 )],
1824 &["foo"],
1825 ),
1826 &[],
1827 RunOptions {
1828 debug: false,
1829 profile: true,
1830 },
1831 )
1832 .await
1833 .unwrap();
1834
1835 assert!(
1836 Regex::new(r"^turtle: duration: \d+ ms\n$")
1837 .unwrap()
1838 .is_match(&console.stderr())
1839 );
1840 }
1841
1842 #[tokio::test]
1843 async fn rebuild_on_update_of_header_dependency() {
1844 let command_runner = FakeCommandRunner::default();
1845 let file_system = FakeFileSystem::default();
1846 let context = create_context(&command_runner, &Default::default(), &file_system);
1847 let config = create_simple_config(
1848 vec![explicit_build(
1849 vec!["foo.o".into()],
1850 Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
1851 HeaderDependency::Make {
1852 path: "foo.d".into(),
1853 },
1854 )),
1855 vec!["foo.c".into()],
1856 )],
1857 &["foo.o"],
1858 );
1859
1860 file_system.write_file("foo.o", "");
1861 file_system.write_file("foo.c", "");
1862 file_system.write_file("foo.h", "");
1863 file_system.write_file("foo.d", "foo.o: foo.h\n");
1864
1865 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1866 .await
1867 .unwrap();
1868 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1869 .await
1870 .unwrap();
1871
1872 file_system.write_file("foo.h", "foo");
1873
1874 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1875
1876 assert_eq!(command_runner.commands(), ["cc foo.c", "cc foo.c"]);
1877 }
1878
1879 #[tokio::test]
1880 async fn read_and_remove_gcc_depfile() {
1881 let file_system = FakeFileSystem::default();
1882 let context = create_context(&Default::default(), &Default::default(), &file_system);
1883 let build = explicit_build(
1884 vec!["foo.o".into()],
1885 Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
1886 HeaderDependency::Gcc {
1887 path: "foo.d".into(),
1888 },
1889 )),
1890 vec!["foo.c".into()],
1891 );
1892
1893 file_system.write_file("foo.c", "");
1894 file_system.write_file("foo.h", "");
1895 file_system.write_file("foo.d", "foo.o: foo.h\n");
1896
1897 run(
1898 &context,
1899 create_simple_config(vec![build.clone()], &["foo.o"]),
1900 &[],
1901 DEFAULT_OPTIONS,
1902 )
1903 .await
1904 .unwrap();
1905
1906 assert_eq!(
1907 context
1908 .database()
1909 .get_header_dependencies(build.id())
1910 .unwrap(),
1911 ["foo.h"]
1912 );
1913 assert!(
1914 !context
1915 .file_system()
1916 .exists("foo.d".as_ref())
1917 .await
1918 .unwrap()
1919 );
1920 }
1921
1922 #[tokio::test]
1923 async fn read_msvc_header_dependencies() {
1924 let console = FakeConsole::default();
1925 let file_system = FakeFileSystem::default();
1926 let context = create_context(
1927 &FakeCommandRunner::new(
1928 [(
1929 "cl foo.c".into(),
1930 Output {
1931 status: ExitStatus::default(),
1932 stdout: b"Note: including file: foo.h\nfoo.c\n".into(),
1933 stderr: vec![],
1934 },
1935 )]
1936 .into_iter()
1937 .collect(),
1938 ),
1939 &console,
1940 &file_system,
1941 );
1942 let build = explicit_build(
1943 vec!["foo.obj".into()],
1944 Rule::new("cl foo.c".into(), None).with_header_dependency(Some(
1945 HeaderDependency::Msvc {
1946 prefix: "Note: including file: ".into(),
1947 },
1948 )),
1949 vec!["foo.c".into()],
1950 );
1951
1952 file_system.write_file("foo.c", "");
1953 file_system.write_file("foo.h", "");
1954
1955 run(
1956 &context,
1957 create_simple_config(vec![build.clone()], &["foo.obj"]),
1958 &[],
1959 DEFAULT_OPTIONS,
1960 )
1961 .await
1962 .unwrap();
1963
1964 assert_eq!(
1965 context
1966 .database()
1967 .get_header_dependencies(build.id())
1968 .unwrap(),
1969 ["foo.h"]
1970 );
1971 assert_eq!(console.stdout(), "foo.c\n");
1972 }
1973
1974 #[tokio::test]
1975 async fn build_generated_header_dependency() {
1976 let command_runner = FakeCommandRunner::default();
1977 let file_system = FakeFileSystem::default();
1978 let context = create_context(&command_runner, &Default::default(), &file_system);
1979 let build = explicit_build(
1980 vec!["foo.o".into()],
1981 Rule::new("cc foo.c".into(), None),
1982 vec!["foo.c".into()],
1983 );
1984
1985 context
1986 .database()
1987 .set_header_dependencies(build.id(), &["foo.h".into()])
1988 .unwrap();
1989 file_system.write_file("foo.c", "");
1990 file_system.write_file("foo.h", "");
1991
1992 run(
1993 &context,
1994 create_simple_config(
1995 vec![
1996 build,
1997 explicit_build(
1998 vec!["foo.h".into()],
1999 Rule::new("touch foo.h".into(), None),
2000 vec![],
2001 ),
2002 ],
2003 &["foo.o"],
2004 ),
2005 &[],
2006 DEFAULT_OPTIONS,
2007 )
2008 .await
2009 .unwrap();
2010
2011 assert_eq!(command_runner.commands(), ["touch foo.h", "cc foo.c"]);
2012 }
2013
2014 #[tokio::test]
2015 async fn ignore_missing_header_dependency() {
2016 let file_system = FakeFileSystem::default();
2017 let context = create_context(&Default::default(), &Default::default(), &file_system);
2018 let build = explicit_build(
2019 vec!["foo.o".into()],
2020 Rule::new("cc foo.c".into(), None),
2021 vec!["foo.c".into()],
2022 );
2023
2024 context
2025 .database()
2026 .set_header_dependencies(build.id(), &["foo.h".into()])
2027 .unwrap();
2028 file_system.write_file("foo.c", "");
2029
2030 run(
2031 &context,
2032 create_simple_config(vec![build], &["foo.o"]),
2033 &[],
2034 DEFAULT_OPTIONS,
2035 )
2036 .await
2037 .unwrap();
2038 }
2039
2040 #[tokio::test]
2041 async fn read_header_dependencies_once() {
2042 let database = FakeDatabase::default();
2043 let file_system = FakeFileSystem::default();
2044 let context = Arc::new(Context::new(
2045 FakeCommandRunner::default(),
2046 Mutex::new(FakeConsole::default()).into(),
2047 database.clone(),
2048 file_system.clone(),
2049 ));
2050 let build = explicit_build(
2051 vec!["foo.o".into()],
2052 Rule::new("cc foo.c".into(), None),
2053 vec!["foo.c".into()],
2054 );
2055
2056 context
2057 .database()
2058 .set_header_dependencies(build.id(), &["foo.h".into()])
2059 .unwrap();
2060 file_system.write_file("foo.c", "");
2061 file_system.write_file("foo.h", "");
2062
2063 run(
2064 &context,
2065 create_simple_config(vec![build.clone()], &["foo.o"]),
2066 &[],
2067 DEFAULT_OPTIONS,
2068 )
2069 .await
2070 .unwrap();
2071
2072 assert_eq!(database.header_dependency_requests(), [build.id()]);
2073 }
2074
2075 #[tokio::test]
2076 async fn detect_circular_header_dependency() {
2077 let context = create_context(
2078 &Default::default(),
2079 &Default::default(),
2080 &Default::default(),
2081 );
2082 let build = explicit_build(
2083 vec!["foo".into()],
2084 Rule::new("touch foo".into(), None),
2085 vec![],
2086 );
2087
2088 context
2089 .database()
2090 .set_header_dependencies(build.id(), &["foo".into()])
2091 .unwrap();
2092
2093 assert_eq!(
2094 run(
2095 &context,
2096 create_simple_config(vec![build], &["foo"]),
2097 &[],
2098 DEFAULT_OPTIONS,
2099 )
2100 .await,
2101 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
2102 vec!["foo".into()]
2103 )))
2104 );
2105 }
2106
2107 #[tokio::test]
2108 async fn build_dynamic_input() {
2109 let command_runner = FakeCommandRunner::default();
2110 let file_system = FakeFileSystem::default();
2111
2112 file_system.write_file(
2113 "foo.dd",
2114 "ninja_dyndep_version = 1\nbuild foo: dyndep | bar\n",
2115 );
2116 file_system.write_file("bar", "");
2117
2118 run(
2119 &create_context(&command_runner, &Default::default(), &file_system),
2120 create_simple_config(
2121 vec![
2122 Build::new(
2123 vec!["foo".into()],
2124 vec![],
2125 Rule::new("touch foo".into(), None).into(),
2126 vec![],
2127 vec!["foo.dd".into()],
2128 Some("foo.dd".into()),
2129 ),
2130 explicit_build(
2131 vec!["foo.dd".into()],
2132 Rule::new("touch foo.dd".into(), None),
2133 vec![],
2134 ),
2135 explicit_build(
2136 vec!["bar".into()],
2137 Rule::new("touch bar".into(), None),
2138 vec![],
2139 ),
2140 ],
2141 &["foo"],
2142 ),
2143 &[],
2144 DEFAULT_OPTIONS,
2145 )
2146 .await
2147 .unwrap();
2148
2149 assert_eq!(
2150 command_runner.commands(),
2151 ["touch foo.dd", "touch bar", "touch foo"]
2152 );
2153 }
2154
2155 #[tokio::test]
2156 async fn build_dynamic_input_declared_for_implicit_output() {
2157 let command_runner = FakeCommandRunner::default();
2158 let file_system = FakeFileSystem::default();
2159
2160 file_system.write_file(
2161 "foo.dd",
2162 "ninja_dyndep_version = 1\nbuild baz: dyndep | bar\n",
2163 );
2164 file_system.write_file("bar", "");
2165
2166 run(
2167 &create_context(&command_runner, &Default::default(), &file_system),
2168 create_simple_config(
2169 vec![
2170 Build::new(
2171 vec!["foo".into()],
2172 vec!["baz".into()],
2173 Rule::new("touch foo".into(), None).into(),
2174 vec![],
2175 vec!["foo.dd".into()],
2176 Some("foo.dd".into()),
2177 ),
2178 explicit_build(
2179 vec!["foo.dd".into()],
2180 Rule::new("touch foo.dd".into(), None),
2181 vec![],
2182 ),
2183 explicit_build(
2184 vec!["bar".into()],
2185 Rule::new("touch bar".into(), None),
2186 vec![],
2187 ),
2188 ],
2189 &["foo"],
2190 ),
2191 &[],
2192 DEFAULT_OPTIONS,
2193 )
2194 .await
2195 .unwrap();
2196
2197 assert_eq!(
2198 command_runner.commands(),
2199 ["touch foo.dd", "touch bar", "touch foo"]
2200 );
2201 }
2202
2203 #[tokio::test]
2204 async fn fail_with_missing_dynamic_dependency() {
2205 let file_system = FakeFileSystem::default();
2206 let build = Build::new(
2207 vec!["foo".into()],
2208 vec![],
2209 Rule::new("touch foo".into(), None).into(),
2210 vec![],
2211 vec![],
2212 Some("foo.dd".into()),
2213 );
2214
2215 file_system.write_file("foo.dd", "ninja_dyndep_version = 1\n");
2216
2217 assert_eq!(
2218 run(
2219 &create_context(&Default::default(), &Default::default(), &file_system),
2220 create_simple_config(vec![build.clone()], &["foo"]),
2221 &[],
2222 DEFAULT_OPTIONS,
2223 )
2224 .await,
2225 Err(BuildError::DynamicDependencyNotFound(build.into()))
2226 );
2227 }
2228
2229 #[tokio::test]
2230 async fn fail_with_unknown_output_in_dynamic_dependency() {
2231 let file_system = FakeFileSystem::default();
2232
2233 file_system.write_file(
2234 "foo.dd",
2235 "ninja_dyndep_version = 1\nbuild bar: dyndep | baz\n",
2236 );
2237
2238 assert_eq!(
2239 run(
2240 &create_context(&Default::default(), &Default::default(), &file_system),
2241 create_simple_config(
2242 vec![Build::new(
2243 vec!["foo".into()],
2244 vec![],
2245 Rule::new("touch foo".into(), None).into(),
2246 vec![],
2247 vec![],
2248 Some("foo.dd".into()),
2249 )],
2250 &["foo"],
2251 ),
2252 &[],
2253 DEFAULT_OPTIONS,
2254 )
2255 .await,
2256 Err(BuildError::BuildGraph(BuildGraphError::OutputNotFound(
2257 "bar".into()
2258 )))
2259 );
2260 }
2261
2262 #[tokio::test]
2263 async fn detect_circular_dynamic_dependency() {
2264 let file_system = FakeFileSystem::default();
2265
2266 file_system.write_file(
2267 "foo.dd",
2268 "ninja_dyndep_version = 1\nbuild foo: dyndep | foo\n",
2269 );
2270
2271 assert_eq!(
2272 run(
2273 &create_context(&Default::default(), &Default::default(), &file_system),
2274 create_simple_config(
2275 vec![Build::new(
2276 vec!["foo".into()],
2277 vec![],
2278 Rule::new("touch foo".into(), None).into(),
2279 vec![],
2280 vec![],
2281 Some("foo.dd".into()),
2282 )],
2283 &["foo"],
2284 ),
2285 &[],
2286 DEFAULT_OPTIONS,
2287 )
2288 .await,
2289 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
2290 vec!["foo".into()]
2291 )))
2292 );
2293 }
2294
2295 #[tokio::test]
2296 async fn run_builds_concurrently() {
2297 let command_runner = FakeCommandRunner::default();
2298
2299 run(
2300 &create_context(&command_runner, &Default::default(), &Default::default()),
2301 create_simple_config(
2302 vec![
2303 pool_build("foo", None),
2304 pool_build("bar", None),
2305 pool_build("baz", None),
2306 ],
2307 &["foo", "bar", "baz"],
2308 ),
2309 &[],
2310 DEFAULT_OPTIONS,
2311 )
2312 .await
2313 .unwrap();
2314
2315 assert_eq!(command_runner.max_concurrency(), 3);
2316 }
2317
2318 #[tokio::test]
2319 async fn limit_concurrency_of_builds_in_pool() {
2320 let command_runner = FakeCommandRunner::default();
2321
2322 run(
2323 &create_context(&command_runner, &Default::default(), &Default::default()),
2324 create_pool_config(
2325 vec![
2326 pool_build("foo", limited_pool("qux")),
2327 pool_build("bar", limited_pool("qux")),
2328 pool_build("baz", limited_pool("qux")),
2329 ],
2330 &["foo", "bar", "baz"],
2331 &[("qux", 1)],
2332 ),
2333 &[],
2334 DEFAULT_OPTIONS,
2335 )
2336 .await
2337 .unwrap();
2338
2339 assert_eq!(command_runner.commands().len(), 3);
2340 assert_eq!(command_runner.max_concurrency(), 1);
2341 }
2342
2343 #[tokio::test]
2344 async fn limit_concurrency_of_builds_in_pool_with_depth_of_two() {
2345 let command_runner = FakeCommandRunner::default();
2346
2347 run(
2348 &create_context(&command_runner, &Default::default(), &Default::default()),
2349 create_pool_config(
2350 vec![
2351 pool_build("foo", limited_pool("quux")),
2352 pool_build("bar", limited_pool("quux")),
2353 pool_build("baz", limited_pool("quux")),
2354 pool_build("qux", limited_pool("quux")),
2355 ],
2356 &["foo", "bar", "baz", "qux"],
2357 &[("quux", 2)],
2358 ),
2359 &[],
2360 DEFAULT_OPTIONS,
2361 )
2362 .await
2363 .unwrap();
2364
2365 assert_eq!(command_runner.commands().len(), 4);
2366 assert_eq!(command_runner.max_concurrency(), 2);
2367 }
2368
2369 #[tokio::test]
2370 async fn run_builds_in_different_pools_concurrently() {
2371 let command_runner = FakeCommandRunner::default();
2372
2373 run(
2374 &create_context(&command_runner, &Default::default(), &Default::default()),
2375 create_pool_config(
2376 vec![
2377 pool_build("foo", limited_pool("baz")),
2378 pool_build("bar", limited_pool("qux")),
2379 ],
2380 &["foo", "bar"],
2381 &[("baz", 1), ("qux", 1)],
2382 ),
2383 &[],
2384 DEFAULT_OPTIONS,
2385 )
2386 .await
2387 .unwrap();
2388
2389 assert_eq!(command_runner.max_concurrency(), 2);
2390 }
2391
2392 #[tokio::test]
2393 async fn run_build_in_pool_of_huge_depth() {
2394 let command_runner = FakeCommandRunner::default();
2395
2396 run(
2397 &create_context(&command_runner, &Default::default(), &Default::default()),
2398 create_pool_config(
2399 vec![pool_build("foo", limited_pool("bar"))],
2400 &["foo"],
2401 &[("bar", usize::MAX)],
2402 ),
2403 &[],
2404 DEFAULT_OPTIONS,
2405 )
2406 .await
2407 .unwrap();
2408
2409 assert_eq!(command_runner.commands(), ["touch foo"]);
2410 }
2411
2412 #[tokio::test]
2413 async fn run_build_in_console_pool() {
2414 let command_runner = FakeCommandRunner::new(
2415 [(
2416 "touch foo".into(),
2417 Output {
2418 status: ExitStatus::default(),
2419 stdout: b"bar\n".into(),
2420 stderr: b"baz\n".into(),
2421 },
2422 )]
2423 .into_iter()
2424 .collect(),
2425 );
2426 let console = FakeConsole::default();
2427
2428 run(
2429 &create_context(&command_runner, &console, &Default::default()),
2430 create_simple_config(
2431 vec![explicit_build(
2432 vec!["foo".into()],
2433 Rule::new("touch foo".into(), Some("build foo".into()))
2434 .with_pool(Some(Pool::Console)),
2435 vec![],
2436 )],
2437 &["foo"],
2438 ),
2439 &[],
2440 DEFAULT_OPTIONS,
2441 )
2442 .await
2443 .unwrap();
2444
2445 assert_eq!(command_runner.commands(), Vec::<String>::new());
2446 assert_eq!(command_runner.console_commands(), ["touch foo"]);
2447 assert_eq!(console.stdout(), "");
2448 assert_eq!(console.stderr(), "build foo\n");
2449 }
2450
2451 #[tokio::test]
2452 async fn limit_concurrency_of_builds_in_console_pool() {
2453 let command_runner = FakeCommandRunner::default();
2454
2455 run(
2456 &create_context(&command_runner, &Default::default(), &Default::default()),
2457 create_simple_config(
2458 vec![
2459 pool_build("foo", Some(Pool::Console)),
2460 pool_build("bar", Some(Pool::Console)),
2461 pool_build("baz", Some(Pool::Console)),
2462 ],
2463 &["foo", "bar", "baz"],
2464 ),
2465 &[],
2466 DEFAULT_OPTIONS,
2467 )
2468 .await
2469 .unwrap();
2470
2471 assert_eq!(command_runner.console_commands().len(), 3);
2472 assert_eq!(command_runner.max_concurrency(), 1);
2473 }
2474
2475 #[tokio::test]
2476 async fn write_debug_log_of_failed_build_in_console_pool() {
2477 let command_runner =
2478 FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
2479 let console = FakeConsole::default();
2480
2481 assert_eq!(
2482 run(
2483 &create_context(&command_runner, &console, &Default::default()),
2484 create_simple_config(
2485 vec![explicit_build(
2486 vec!["foo".into()],
2487 Rule::new("exit 1".into(), None).with_pool(Some(Pool::Console)),
2488 vec![],
2489 )],
2490 &["foo"],
2491 ),
2492 &[],
2493 RunOptions {
2494 debug: true,
2495 profile: false,
2496 },
2497 )
2498 .await,
2499 Err(BuildError::Build)
2500 );
2501 assert_eq!(command_runner.console_commands(), ["exit 1"]);
2502 assert_eq!(
2503 console.stderr(),
2504 "turtle: command: exit 1\nturtle: exit status: 1\n"
2505 );
2506 }
2507
2508 #[tokio::test]
2509 async fn lock_console_during_command_in_console_pool() {
2510 let command_runner = FakeCommandRunner::default();
2511 let context = create_run_context(&command_runner, &Default::default(), &[]);
2512 let rule = Rule::new("foo".into(), None).with_pool(Some(Pool::Console));
2513 let mut future = pin!(run_rule(&context, &rule));
2514
2515 assert!(poll!(&mut future).is_pending());
2516 assert_eq!(command_runner.console_commands(), ["foo"]);
2517 assert!(context.build().console().try_lock().is_err());
2518
2519 future.await.unwrap();
2520
2521 assert!(context.build().console().try_lock().is_ok());
2522 }
2523
2524 #[tokio::test]
2525 async fn flush_console_before_command_in_console_pool() {
2526 let command_runner = FakeCommandRunner::default();
2527 let console = FakeConsole::default();
2528 let context = create_run_context(&command_runner, &console, &[]);
2529 let rule = Rule::new("foo".into(), Some("bar".into())).with_pool(Some(Pool::Console));
2530 let mut future = pin!(run_rule(&context, &rule));
2531
2532 assert!(poll!(&mut future).is_pending());
2533 assert_eq!(command_runner.console_commands(), ["foo"]);
2534 assert_eq!(console.flushed_stderr(), "bar\n");
2535
2536 future.await.unwrap();
2537 }
2538
2539 #[tokio::test]
2540 async fn wait_for_console_before_command_in_console_pool() {
2541 let command_runner = FakeCommandRunner::default();
2542 let context = create_run_context(&command_runner, &Default::default(), &[]);
2543 let rule = Rule::new("foo".into(), None).with_pool(Some(Pool::Console));
2544 let console = context.build().console().lock().await;
2545 let mut future = pin!(run_rule(&context, &rule));
2546
2547 for _ in 0..POLL_COUNT {
2548 assert!(poll!(&mut future).is_pending());
2549 }
2550
2551 assert_eq!(command_runner.console_commands(), Vec::<String>::new());
2552
2553 drop(console);
2554 future.await.unwrap();
2555
2556 assert_eq!(command_runner.console_commands(), ["foo"]);
2557 }
2558
2559 #[tokio::test]
2560 async fn run_command_concurrently_with_command_in_console_pool() {
2561 let command_runner = FakeCommandRunner::default();
2562 let context = create_run_context(&command_runner, &Default::default(), &[]);
2563 let foo = Rule::new("foo".into(), None).with_pool(Some(Pool::Console));
2564 let bar = Rule::new("bar".into(), None);
2565 let mut foo_future = pin!(run_rule(&context, &foo));
2566 let mut bar_future = pin!(run_rule(&context, &bar));
2567
2568 assert!(poll!(&mut foo_future).is_pending());
2569 assert!(poll!(&mut bar_future).is_pending());
2570 assert_eq!(command_runner.console_commands(), ["foo"]);
2571 assert_eq!(command_runner.commands(), ["bar"]);
2572
2573 foo_future.await.unwrap();
2574 bar_future.await.unwrap();
2575 }
2576
2577 #[tokio::test]
2578 async fn release_pool_before_waiting_for_console() {
2579 let command_runner = FakeCommandRunner::default();
2580 let context = create_run_context(&command_runner, &Default::default(), &[("baz", 1)]);
2581 let foo = Rule::new("foo".into(), None).with_pool(limited_pool("baz"));
2582 let bar = Rule::new("bar".into(), None).with_pool(limited_pool("baz"));
2583 let console = context.build().console().lock().await;
2584 let mut foo_future = pin!(run_rule(&context, &foo));
2585 let mut bar_future = pin!(run_rule(&context, &bar));
2586
2587 for _ in 0..POLL_COUNT {
2588 assert!(poll!(&mut foo_future).is_pending());
2589 }
2590
2591 for _ in 0..POLL_COUNT {
2592 assert!(poll!(&mut bar_future).is_pending());
2593 }
2594
2595 assert_eq!(command_runner.commands(), ["foo", "bar"]);
2596
2597 drop(console);
2598 foo_future.await.unwrap();
2599 bar_future.await.unwrap();
2600 }
2601
2602 #[tokio::test]
2603 async fn wait_for_pool_before_locking_console() {
2604 let command_runner = FakeCommandRunner::default();
2605 let console = FakeConsole::default();
2606 let context = create_run_context(&command_runner, &console, &[("bar", 1)]);
2607 let rule = Rule::new("foo".into(), Some("foo".into())).with_pool(limited_pool("bar"));
2608 let permit = context.pool(rule.pool()).await.unwrap();
2609 let mut future = pin!(run_rule(&context, &rule));
2610
2611 for _ in 0..POLL_COUNT {
2612 assert!(poll!(&mut future).is_pending());
2613 }
2614
2615 assert_eq!(command_runner.commands(), Vec::<String>::new());
2616 assert_eq!(console.stderr(), "");
2617 assert!(context.build().console().try_lock().is_ok());
2618
2619 drop(permit);
2620 future.await.unwrap();
2621
2622 assert_eq!(command_runner.commands(), ["foo"]);
2623 assert_eq!(console.stderr(), "foo\n");
2624 }
2625
2626 #[tokio::test]
2627 async fn keep_pool_until_command_finishes_on_console_error() {
2628 let command_runner = FakeCommandRunner::default();
2629 let context = create_run_context(&command_runner, &FakeConsole::failing(), &[("baz", 1)]);
2630 let foo = Rule::new("foo".into(), Some("foo".into())).with_pool(limited_pool("baz"));
2631 let bar = Rule::new("bar".into(), None).with_pool(limited_pool("baz"));
2632 let mut foo_future = pin!(run_rule(&context, &foo));
2633 let mut bar_future = pin!(run_rule(&context, &bar));
2634
2635 assert!(poll!(&mut foo_future).is_pending());
2636 assert!(poll!(&mut bar_future).is_pending());
2637 assert_eq!(command_runner.commands(), ["foo"]);
2638 assert!(foo_future.await.is_err());
2639 }
2640
2641 #[test]
2642 fn classify_phony_and_file_inputs() {
2643 let build = explicit_build(
2644 vec!["foo".into()],
2645 Rule::new("".into(), None),
2646 vec!["bar".into(), "baz".into(), "qux".into(), "bar".into()],
2647 );
2648 let context = RunContext::new(
2649 create_context(
2650 &Default::default(),
2651 &Default::default(),
2652 &Default::default(),
2653 ),
2654 create_simple_config(
2655 vec![
2656 build.clone(),
2657 Build::new(vec!["bar".into()], vec![], None, vec![], vec![], None),
2658 explicit_build(vec!["baz".into()], Rule::new("".into(), None), vec![]),
2659 ],
2660 &[],
2661 ),
2662 BuildGraph::new(&Default::default()),
2663 Default::default(),
2664 DEFAULT_OPTIONS,
2665 );
2666
2667 assert_eq!(
2668 classify_inputs(
2669 &context,
2670 &build,
2671 &["quux".into(), "baz".into()],
2672 &["qux", "corge"],
2673 ),
2674 (vec!["bar"], vec!["baz", "qux", "quux", "corge"])
2675 );
2676 }
2677}