Skip to main content

turtle_build/
run.rs

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_inputs},
12    log::{debug, profile},
13};
14use crate::{
15    build_graph::{BuildGraph, BuildGraphError},
16    build_hash::BuildHash,
17    compile::compile_dynamic,
18    context::Context,
19    error::BuildError,
20    infrastructure::{Console, Metadata},
21    ir::{Build, Config, DynamicConfig, Pool, Rule},
22    parse::parse_dynamic,
23};
24use alloc::sync::Arc;
25use async_recursion::async_recursion;
26use core::hash::BuildHasherDefault;
27use futures::{
28    StreamExt, TryStreamExt,
29    future::{FutureExt, try_join_all},
30    stream,
31};
32use itertools::Itertools;
33pub use options::RunOptions;
34use rapidhash::{RapidHashMap, fast::RapidHasher};
35use std::{collections::HashSet, path::Path, process::Output};
36use tokio::{spawn, sync::MutexGuard, time::Instant, try_join};
37
38// Only this many requested outputs are started at once so that finished builds free their tasks.
39// TODO Consider using no `async` and a normal thread pool (e.g. `rayon`).
40const OUTPUT_CONCURRENCY: usize = 256;
41
42/// Runs builds.
43pub async fn run(
44    context: &Arc<Context>,
45    config: Arc<Config>,
46    outputs: &[String],
47    options: RunOptions,
48) -> Result<(), BuildError> {
49    let mut graph = BuildGraph::new(config.outputs());
50    let mut header_inputs = RapidHashMap::default();
51
52    for build in config
53        .outputs()
54        .values()
55        .filter(|build| build.rule().is_some())
56        .unique_by(|build| build.id())
57    {
58        let inputs = context.database().get_header_inputs(build.id()).await?;
59
60        graph.add_inputs(&build.outputs()[0], &inputs);
61        header_inputs.insert(build.id(), inputs);
62    }
63
64    let context = Arc::new(RunContext::new(
65        context.clone(),
66        config,
67        graph,
68        header_inputs,
69        options,
70    ));
71
72    if let Err(error) = context.build_graph().lock().await.validate() {
73        return Err(map_build_graph_error(&context, &error).await);
74    }
75
76    stream::iter(
77        if outputs.is_empty() {
78            context
79                .config()
80                .default_outputs()
81                .iter()
82                .map(|output| {
83                    context
84                        .config()
85                        .outputs()
86                        .get(output.as_ref())
87                        .ok_or_else(|| BuildError::DefaultOutputNotFound(output.clone()))
88                })
89                .collect::<Result<Vec<_>, _>>()?
90        } else {
91            outputs
92                .iter()
93                .map(|output| {
94                    context
95                        .config()
96                        .outputs()
97                        .get(output.as_str())
98                        .ok_or_else(|| BuildError::OutputNotFound(output.clone()))
99                })
100                .collect::<Result<Vec<_>, _>>()?
101        }
102        .into_iter()
103        .map(|build| run_build(context.clone(), build)),
104    )
105    .buffer_unordered(OUTPUT_CONCURRENCY)
106    .try_for_each(async |_| Ok(()))
107    .await?;
108
109    Ok(())
110}
111
112#[async_recursion]
113async fn run_build(context: Arc<RunContext>, build: &Arc<Build>) -> Result<bool, BuildError> {
114    // Do not inline this to avoid holding a lock of build futures across an await point.
115    let future = if let Some(future) = context
116        .build_futures()
117        .peek_with(&build.id(), |_, future| future.clone())
118    {
119        future
120    } else {
121        context
122            .build_futures()
123            .entry_async(build.id())
124            .await
125            .or_insert_with(|| spawn_build(context.clone(), build.clone()).boxed().shared())
126            .get()
127            .clone()
128    };
129
130    future.await
131}
132
133async fn spawn_build(context: Arc<RunContext>, build: Arc<Build>) -> Result<bool, BuildError> {
134    spawn(async move {
135        let (inputs_stale, _, output_metadata) = try_join!(
136            build_inputs(&context, build.inputs()),
137            build_inputs(&context, build.order_only_inputs()),
138            read_output_metadata(&context, &build),
139        )?;
140
141        let dynamic_inputs = if let Some(path) = build.dynamic_module() {
142            if build_input(context.clone(), path).await? {
143                return skip_build(&context, &build).await;
144            }
145
146            let config = load_dynamic_config(&context, path).await?;
147
148            build
149                .outputs()
150                .iter()
151                .chain(build.implicit_outputs())
152                .find_map(|output| config.outputs().get(output.as_ref()))
153                .map(|build| build.inputs())
154                .ok_or_else(|| BuildError::DynamicDependencyNotFound(build.clone()))?
155        } else {
156            &[]
157        };
158
159        let dynamic_inputs_stale = build_inputs(&context, dynamic_inputs).await?;
160
161        if build.rule().is_none() {
162            // TODO Consider dropping this case by assuming that outputs of phony
163            // rules are always virtual.
164            invalidate_outputs(&context, &build).await;
165        }
166
167        let header_inputs = context.header_inputs(build.id());
168
169        let header_inputs_stale = try_join_all(
170            header_inputs
171                .iter()
172                .filter_map(|input| context.config().outputs().get(input))
173                .map(|build| run_build(context.clone(), build)),
174        )
175        .await?
176        .contains(&true);
177
178        if inputs_stale || dynamic_inputs_stale || header_inputs_stale {
179            return skip_build(&context, &build).await;
180        }
181
182        let header_inputs = filter_existing_header_inputs(&context, header_inputs).await?;
183
184        let (phony_inputs, file_inputs) =
185            classify_inputs(&context, &build, dynamic_inputs, &header_inputs);
186        let hash = context.build().database().get_hash(build.id()).await?;
187        let mut timestamp_hash =
188            calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
189        let outputs_exist = output_metadata.is_some() || build.rule().is_none();
190
191        if outputs_exist && Some(timestamp_hash) == hash.map(|hash| hash.timestamp()) {
192            if let Some(metadata) = &output_metadata {
193                cache_output_metadata(&context, &build, metadata).await;
194            }
195
196            return Ok(false);
197        }
198
199        let mut content_hash =
200            calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
201
202        if outputs_exist && hash.map(|hash| hash.content()) == Some(content_hash) {
203            return Ok(false);
204        } else if context.options().dry_run {
205            return skip_build(&context, &build).await;
206        } else if let Some(rule) = build.rule() {
207            try_join_all(
208                build
209                    .outputs()
210                    .iter()
211                    .chain(build.implicit_outputs())
212                    .map(|path| prepare_directory(&context, path.as_ref())),
213            )
214            .await?;
215
216            let output = run_rule(&context, rule).await;
217
218            invalidate_outputs(&context, &build).await;
219
220            let new_header_inputs = read_header_inputs(&context, rule, &output?).await?;
221
222            context
223                .build()
224                .database()
225                .set_header_inputs(build.id(), &new_header_inputs)
226                .await?;
227
228            for output in build.outputs() {
229                context
230                    .build()
231                    .database()
232                    .set_output(
233                        output,
234                        context.config().source_map().get(output).map(AsRef::as_ref),
235                    )
236                    .await?;
237            }
238
239            if header_inputs.iter().copied().ne(&new_header_inputs) {
240                let header_inputs =
241                    filter_existing_header_inputs(&context, &new_header_inputs).await?;
242                let (phony_inputs, file_inputs) =
243                    classify_inputs(&context, &build, dynamic_inputs, &header_inputs);
244
245                timestamp_hash =
246                    calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
247                content_hash =
248                    calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
249            }
250        }
251
252        context
253            .build()
254            .database()
255            .set_hash(build.id(), BuildHash::new(timestamp_hash, content_hash))
256            .await?;
257
258        Ok(false)
259    })
260    .await?
261}
262
263async fn build_inputs(context: &Arc<RunContext>, inputs: &[Arc<str>]) -> Result<bool, BuildError> {
264    Ok(try_join_all(
265        inputs
266            .iter()
267            .map(|input| build_input(context.clone(), input)),
268    )
269    .await?
270    .contains(&true))
271}
272
273async fn build_input(context: Arc<RunContext>, input: &Arc<str>) -> Result<bool, BuildError> {
274    if let Some(build) = context.config().outputs().get(input) {
275        run_build(context.clone(), build).await
276    } else {
277        check_file_existence(&context, input).await?;
278
279        Ok(false)
280    }
281}
282
283async fn load_dynamic_config<'a>(
284    context: &'a RunContext,
285    path: &str,
286) -> Result<&'a DynamicConfig, BuildError> {
287    context
288        .dynamic_config(path)
289        .get_or_try_init(|| async {
290            let config = compile_dynamic(
291                &parse_dynamic(
292                    &context
293                        .build()
294                        .file_system()
295                        .read_file_to_string(path.as_ref())
296                        .await?,
297                )?,
298                context.build().path_pool(),
299            )?;
300
301            if let Err(error) = context.build_graph().lock().await.validate_dynamic(&config) {
302                return Err(map_build_graph_error(context, &error).await);
303            }
304
305            Ok(config)
306        })
307        .await
308}
309
310async fn read_output_metadata(
311    context: &RunContext,
312    build: &Build,
313) -> Result<Option<Vec<Metadata>>, BuildError> {
314    Ok(try_join_all(
315        build
316            .outputs()
317            .iter()
318            .chain(build.implicit_outputs())
319            .map(|path| {
320                context
321                    .build()
322                    .file_system()
323                    .metadata(path.as_ref().as_ref())
324            }),
325    )
326    .await?
327    .into_iter()
328    .collect())
329}
330
331async fn cache_output_metadata(context: &RunContext, build: &Build, metadata: &[Metadata]) {
332    // Inputs of phony builds might write their outputs.
333    if build.rule().is_none() {
334        return;
335    }
336
337    for (path, &metadata) in build
338        .outputs()
339        .iter()
340        .chain(build.implicit_outputs())
341        .zip(metadata)
342    {
343        context.file_cache().set_metadata(path, metadata).await;
344    }
345}
346
347async fn filter_existing_header_inputs<'a>(
348    context: &RunContext,
349    dependencies: &'a [Arc<str>],
350) -> Result<Vec<&'a Arc<str>>, BuildError> {
351    let mut existing_dependencies = vec![];
352
353    for dependency in dependencies {
354        if context.file_cache().exists(dependency).await? {
355            existing_dependencies.push(dependency);
356        }
357    }
358
359    Ok(existing_dependencies)
360}
361
362async fn check_file_existence(context: &RunContext, path: &Arc<str>) -> Result<(), BuildError> {
363    if !context.file_cache().exists(path).await? {
364        return Err(BuildError::FileNotFound(
365            context
366                .build()
367                .database()
368                .get_source(path)
369                .await?
370                .unwrap_or_else(|| path.as_ref().into()),
371        ));
372    }
373
374    Ok(())
375}
376
377async fn prepare_directory(context: &RunContext, path: impl AsRef<Path>) -> Result<(), BuildError> {
378    if let Some(directory) = path.as_ref().parent() {
379        context
380            .build()
381            .file_system()
382            .create_directory(directory)
383            .await?;
384    }
385
386    Ok(())
387}
388
389async fn invalidate_outputs(context: &RunContext, build: &Build) {
390    for output in build.outputs().iter().chain(build.implicit_outputs()) {
391        context.file_cache().invalidate(output).await;
392    }
393}
394
395fn classify_inputs<'a>(
396    context: &'a RunContext,
397    build: &'a Build,
398    dynamic_inputs: &'a [Arc<str>],
399    header_inputs: &'a [&'a Arc<str>],
400) -> (Vec<&'a Arc<str>>, Vec<&'a Arc<str>>) {
401    let (phony_inputs, file_inputs) = build
402        .inputs()
403        .iter()
404        .chain(dynamic_inputs)
405        .unique()
406        .partition::<Vec<_>, _>(|&input| {
407            context
408                .config()
409                .outputs()
410                .get(input)
411                .map_or_default(|build| build.rule().is_none())
412        });
413
414    (
415        phony_inputs,
416        file_inputs
417            .into_iter()
418            .chain(header_inputs.iter().copied())
419            // Use a non-random hasher to keep input orders the same across runs for build hashes.
420            .collect::<HashSet<_, BuildHasherDefault<RapidHasher>>>()
421            .into_iter()
422            .collect(),
423    )
424}
425
426async fn skip_build(context: &RunContext, build: &Build) -> Result<bool, BuildError> {
427    if let Some(rule) = build.rule() {
428        let _guard = write_description(context, rule).await?;
429    }
430
431    Ok(true)
432}
433
434async fn run_rule(context: &RunContext, rule: &Rule) -> Result<Output, BuildError> {
435    let ((output, duration), mut console) = if rule.pool() == Some(&Pool::Console) {
436        let mut console = write_description(context, rule).await?;
437
438        console.flush().await?;
439
440        let time = Instant::now();
441        let status = context
442            .build()
443            .command_runner()
444            .run_with_console(rule.command())
445            .await?;
446
447        (
448            (
449                Output {
450                    status,
451                    stdout: vec![],
452                    stderr: vec![],
453                },
454                Instant::now() - time,
455            ),
456            console,
457        )
458    } else {
459        let permit = context.pool(rule.pool()).await?;
460        let time = Instant::now();
461        let output = context.build().command_runner().run(rule.command()).await?;
462
463        drop(permit);
464
465        (
466            (output, Instant::now() - time),
467            write_description(context, rule).await?,
468        )
469    };
470
471    profile!(context, console, "duration: {} ms", duration.as_millis());
472
473    console
474        .write_stdout(&exclude_show_includes(rule, &output.stdout))
475        .await?;
476    console.write_stderr(&output.stderr).await?;
477
478    if !output.status.success() {
479        debug!(
480            context,
481            console,
482            "exit status: {}",
483            output
484                .status
485                .code()
486                .map(|code| code.to_string())
487                .unwrap_or_else(|| "-".into())
488        );
489
490        return Err(BuildError::Build);
491    }
492
493    Ok(output)
494}
495
496async fn write_description<'a>(
497    context: &'a RunContext,
498    rule: &Rule,
499) -> Result<MutexGuard<'a, dyn Console + Send + Sync>, BuildError> {
500    let mut console = context.build().console().lock().await;
501
502    if let Some(description) = rule.description() {
503        console.write_stdout(description.as_bytes()).await?;
504        console.write_stdout(b"\n").await?;
505    }
506
507    debug!(context, console, "command: {}", rule.command());
508
509    Ok(console)
510}
511
512async fn map_build_graph_error(context: &RunContext, error: &BuildGraphError) -> BuildError {
513    match error {
514        BuildGraphError::CircularDependency(outputs) => {
515            match try_join_all(outputs.iter().map(|output| async {
516                Ok::<_, BuildError>(
517                    context
518                        .build()
519                        .database()
520                        .get_source(output)
521                        .await?
522                        .map(|string| string.into())
523                        .unwrap_or_else(|| output.clone()),
524                )
525            }))
526            .await
527            {
528                Ok(outputs) => {
529                    BuildGraphError::CircularDependency(outputs.into_iter().dedup().collect())
530                        .into()
531                }
532                Err(error) => error,
533            }
534        }
535        BuildGraphError::OutputNotFound(_) => error.clone().into(),
536    }
537}
538
539#[cfg(test)]
540mod tests {
541    use super::*;
542    use crate::{
543        infrastructure::{FakeCommandRunner, FakeConsole, FakeDatabase, FakeFileSystem, FileError},
544        ir::HeaderDependency,
545    };
546    use core::{num::NonZeroUsize, pin::pin, task::Poll};
547    use futures::poll;
548    use pretty_assertions::{assert_eq, assert_ne};
549    use regex::Regex;
550    #[cfg(unix)]
551    use std::os::unix::process::ExitStatusExt;
552    #[cfg(windows)]
553    use std::os::windows::process::ExitStatusExt;
554    use std::process::ExitStatus;
555    use tokio::{sync::Mutex, task::yield_now};
556
557    const DEFAULT_OPTIONS: RunOptions = RunOptions {
558        debug: false,
559        dry_run: false,
560        profile: false,
561    };
562    const POLL_COUNT: usize = 8;
563
564    fn create_context(
565        command_runner: &FakeCommandRunner,
566        console: &FakeConsole,
567        file_system: &FakeFileSystem,
568    ) -> Arc<Context> {
569        Context::new(
570            command_runner.clone(),
571            Mutex::new(console.clone()).into(),
572            FakeDatabase::default(),
573            file_system.clone(),
574            Default::default(),
575        )
576        .into()
577    }
578
579    fn create_outputs(builds: Vec<Build>) -> RapidHashMap<Arc<str>, Arc<Build>> {
580        builds
581            .into_iter()
582            .map(Arc::new)
583            .flat_map(|build| {
584                build
585                    .outputs()
586                    .iter()
587                    .chain(build.implicit_outputs())
588                    .map(|output| (output.clone(), build.clone()))
589                    .collect::<Vec<_>>()
590            })
591            .collect()
592    }
593
594    fn create_simple_config(builds: Vec<Build>, default_outputs: &[&str]) -> Arc<Config> {
595        create_pool_config(builds, default_outputs, &[])
596    }
597
598    fn create_pool_config(
599        builds: Vec<Build>,
600        default_outputs: &[&str],
601        pools: &[(&str, usize)],
602    ) -> Arc<Config> {
603        Config::new(
604            create_outputs(builds),
605            default_outputs
606                .iter()
607                .map(|&output| output.into())
608                .collect(),
609            Default::default(),
610            pools
611                .iter()
612                .map(|&(name, depth)| (name.into(), NonZeroUsize::new(depth).unwrap()))
613                .collect(),
614            None,
615        )
616        .into()
617    }
618
619    fn explicit_build(outputs: Vec<Arc<str>>, rule: Rule, inputs: Vec<Arc<str>>) -> Build {
620        Build::new(outputs, vec![], rule.into(), inputs, vec![], None)
621    }
622
623    fn failed_output() -> Output {
624        Output {
625            status: ExitStatus::from_raw(cfg_select! {
626                unix => 1 << 8,
627                windows => 1,
628            }),
629            stdout: vec![],
630            stderr: vec![],
631        }
632    }
633
634    fn limited_pool(name: &str) -> Option<Pool> {
635        Some(Pool::Limited(name.into()))
636    }
637
638    fn pool_build(output: &str, pool: Option<Pool>) -> Build {
639        explicit_build(
640            vec![output.into()],
641            Rule::new(format!("touch {output}"), None).with_pool(pool),
642            vec![],
643        )
644    }
645
646    fn count_metadata_requests(file_system: &FakeFileSystem, path: &str) -> usize {
647        file_system
648            .metadata_requests()
649            .iter()
650            .filter(|request| request.as_path() == Path::new(path))
651            .count()
652    }
653
654    fn create_run_context(
655        command_runner: &FakeCommandRunner,
656        console: &FakeConsole,
657        pools: &[(&str, usize)],
658    ) -> RunContext {
659        RunContext::new(
660            create_context(command_runner, console, &Default::default()),
661            create_pool_config(vec![], &[], pools),
662            BuildGraph::new(&Default::default()),
663            Default::default(),
664            DEFAULT_OPTIONS,
665        )
666    }
667
668    fn create_build_run_context(
669        command_runner: &FakeCommandRunner,
670        file_system: &FakeFileSystem,
671        builds: Vec<Build>,
672    ) -> Arc<RunContext> {
673        RunContext::new(
674            create_context(command_runner, &Default::default(), file_system),
675            create_simple_config(builds, &[]),
676            BuildGraph::new(&Default::default()),
677            Default::default(),
678            DEFAULT_OPTIONS,
679        )
680        .into()
681    }
682
683    #[tokio::test]
684    async fn build_nothing() {
685        let command_runner = FakeCommandRunner::default();
686
687        run(
688            &create_context(&command_runner, &Default::default(), &Default::default()),
689            create_simple_config(vec![], &[]),
690            &[],
691            DEFAULT_OPTIONS,
692        )
693        .await
694        .unwrap();
695
696        assert_eq!(command_runner.commands(), Vec::<String>::new());
697    }
698
699    #[tokio::test]
700    async fn build_default_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            &[],
721            DEFAULT_OPTIONS,
722        )
723        .await
724        .unwrap();
725
726        assert_eq!(command_runner.commands(), ["touch foo"]);
727    }
728
729    #[tokio::test]
730    async fn build_specified_output() {
731        let command_runner = FakeCommandRunner::default();
732
733        run(
734            &create_context(&command_runner, &Default::default(), &Default::default()),
735            create_simple_config(
736                vec![
737                    explicit_build(
738                        vec!["foo".into()],
739                        Rule::new("touch foo".into(), None),
740                        vec![],
741                    ),
742                    explicit_build(
743                        vec!["bar".into()],
744                        Rule::new("touch bar".into(), None),
745                        vec![],
746                    ),
747                ],
748                &["foo"],
749            ),
750            &["bar".into()],
751            DEFAULT_OPTIONS,
752        )
753        .await
754        .unwrap();
755
756        assert_eq!(command_runner.commands(), ["touch bar"]);
757    }
758
759    #[tokio::test]
760    async fn build_duplicate_outputs() {
761        let command_runner = FakeCommandRunner::default();
762
763        run(
764            &create_context(&command_runner, &Default::default(), &Default::default()),
765            create_simple_config(
766                vec![explicit_build(
767                    vec!["foo".into()],
768                    Rule::new("touch foo".into(), None),
769                    vec![],
770                )],
771                &[],
772            ),
773            &["foo".into(), "foo".into()],
774            DEFAULT_OPTIONS,
775        )
776        .await
777        .unwrap();
778
779        assert_eq!(command_runner.commands(), ["touch foo"]);
780    }
781
782    #[tokio::test]
783    async fn build_more_outputs_than_concurrency_limit() {
784        let command_runner = FakeCommandRunner::default();
785        let outputs = (0..=OUTPUT_CONCURRENCY)
786            .map(|index| index.to_string())
787            .collect::<Vec<_>>();
788
789        run(
790            &create_context(&command_runner, &Default::default(), &Default::default()),
791            create_simple_config(
792                outputs
793                    .iter()
794                    .map(|output| {
795                        explicit_build(
796                            vec![output.as_str().into()],
797                            Rule::new(format!("touch {output}"), None),
798                            vec![],
799                        )
800                    })
801                    .collect(),
802                &[],
803            ),
804            &outputs,
805            DEFAULT_OPTIONS,
806        )
807        .await
808        .unwrap();
809
810        assert_eq!(
811            command_runner.commands().into_iter().sorted().collect_vec(),
812            outputs
813                .iter()
814                .map(|output| format!("touch {output}"))
815                .sorted()
816                .collect_vec()
817        );
818    }
819
820    #[tokio::test]
821    async fn fail_with_unknown_default_output() {
822        assert_eq!(
823            run(
824                &create_context(
825                    &Default::default(),
826                    &Default::default(),
827                    &Default::default()
828                ),
829                create_simple_config(vec![], &["foo"]),
830                &[],
831                DEFAULT_OPTIONS,
832            )
833            .await,
834            Err(BuildError::DefaultOutputNotFound("foo".into()))
835        );
836    }
837
838    #[tokio::test]
839    async fn fail_with_unknown_output() {
840        assert_eq!(
841            run(
842                &create_context(
843                    &Default::default(),
844                    &Default::default(),
845                    &Default::default()
846                ),
847                create_simple_config(vec![], &[]),
848                &["foo".into()],
849                DEFAULT_OPTIONS,
850            )
851            .await,
852            Err(BuildError::OutputNotFound("foo".into()))
853        );
854    }
855
856    #[tokio::test]
857    async fn fail_with_unknown_output_after_known_output() {
858        let command_runner = FakeCommandRunner::default();
859
860        assert_eq!(
861            run(
862                &create_context(&command_runner, &Default::default(), &Default::default()),
863                create_simple_config(
864                    vec![explicit_build(
865                        vec!["foo".into()],
866                        Rule::new("touch foo".into(), None),
867                        vec![],
868                    )],
869                    &[],
870                ),
871                &["foo".into(), "bar".into()],
872                DEFAULT_OPTIONS,
873            )
874            .await,
875            Err(BuildError::OutputNotFound("bar".into()))
876        );
877
878        // Let the runtime run builds spawned before the error if any.
879        yield_now().await;
880
881        assert_eq!(command_runner.commands(), Vec::<String>::new());
882    }
883
884    #[tokio::test]
885    async fn fail_to_build_one_of_outputs() {
886        let command_runner =
887            FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
888
889        assert_eq!(
890            run(
891                &create_context(&command_runner, &Default::default(), &Default::default()),
892                create_simple_config(
893                    vec![
894                        explicit_build(
895                            vec!["foo".into()],
896                            Rule::new("touch foo".into(), None),
897                            vec![],
898                        ),
899                        explicit_build(
900                            vec!["bar".into()],
901                            Rule::new("exit 1".into(), None),
902                            vec![],
903                        ),
904                    ],
905                    &["foo", "bar"],
906                ),
907                &[],
908                DEFAULT_OPTIONS,
909            )
910            .await,
911            Err(BuildError::Build)
912        );
913    }
914
915    #[tokio::test]
916    async fn build_shared_input_once() {
917        let command_runner = FakeCommandRunner::default();
918        let file_system = FakeFileSystem::default();
919
920        file_system.write_file("bar", "");
921        file_system.write_file("baz", "");
922
923        run(
924            &create_context(&command_runner, &Default::default(), &file_system),
925            create_simple_config(
926                vec![
927                    explicit_build(
928                        vec!["foo".into()],
929                        Rule::new("touch foo".into(), None),
930                        vec!["bar".into(), "baz".into()],
931                    ),
932                    explicit_build(
933                        vec!["bar".into()],
934                        Rule::new("touch bar".into(), None),
935                        vec!["baz".into()],
936                    ),
937                    explicit_build(
938                        vec!["baz".into()],
939                        Rule::new("touch baz".into(), None),
940                        vec![],
941                    ),
942                ],
943                &["foo"],
944            ),
945            &[],
946            DEFAULT_OPTIONS,
947        )
948        .await
949        .unwrap();
950
951        assert_eq!(
952            command_runner.commands(),
953            ["touch baz", "touch bar", "touch foo"]
954        );
955    }
956
957    #[tokio::test]
958    async fn get_started_build_during_entry_lock() {
959        let command_runner = FakeCommandRunner::default();
960        let context = create_build_run_context(&command_runner, &Default::default(), vec![]);
961        let build = explicit_build(
962            vec!["foo".into()],
963            Rule::new("touch foo".into(), None),
964            vec![],
965        )
966        .into();
967
968        run_build(context.clone(), &build).await.unwrap();
969
970        let _entry = context.build_futures().entry_async(build.id()).await;
971
972        assert_eq!(
973            poll!(pin!(run_build(context.clone(), &build))),
974            Poll::Ready(Ok(false))
975        );
976        assert_eq!(command_runner.commands(), ["touch foo"]);
977    }
978
979    #[tokio::test]
980    async fn build_order_only_input() {
981        let command_runner = FakeCommandRunner::default();
982
983        run(
984            &create_context(&command_runner, &Default::default(), &Default::default()),
985            create_simple_config(
986                vec![
987                    Build::new(
988                        vec!["foo".into()],
989                        vec![],
990                        Rule::new("touch foo".into(), None).into(),
991                        vec![],
992                        vec!["bar".into()],
993                        None,
994                    ),
995                    explicit_build(
996                        vec!["bar".into()],
997                        Rule::new("touch bar".into(), None),
998                        vec![],
999                    ),
1000                ],
1001                &["foo"],
1002            ),
1003            &[],
1004            DEFAULT_OPTIONS,
1005        )
1006        .await
1007        .unwrap();
1008
1009        assert_eq!(command_runner.commands(), ["touch bar", "touch foo"]);
1010    }
1011
1012    #[tokio::test]
1013    async fn build_multiple_outputs_of_build() {
1014        let command_runner = FakeCommandRunner::default();
1015        let file_system = FakeFileSystem::default();
1016
1017        file_system.write_file("foo", "");
1018        file_system.write_file("bar", "");
1019
1020        run(
1021            &create_context(&command_runner, &Default::default(), &file_system),
1022            create_simple_config(
1023                vec![
1024                    explicit_build(
1025                        vec!["foo".into(), "bar".into()],
1026                        Rule::new("touch foo bar".into(), None),
1027                        vec![],
1028                    ),
1029                    explicit_build(
1030                        vec!["baz".into()],
1031                        Rule::new("cat foo bar > baz".into(), None),
1032                        vec!["foo".into(), "bar".into()],
1033                    ),
1034                ],
1035                &["baz"],
1036            ),
1037            &[],
1038            DEFAULT_OPTIONS,
1039        )
1040        .await
1041        .unwrap();
1042
1043        assert_eq!(
1044            command_runner.commands(),
1045            ["touch foo bar", "cat foo bar > baz"]
1046        );
1047    }
1048
1049    #[tokio::test]
1050    async fn fail_with_missing_input() {
1051        let command_runner = FakeCommandRunner::default();
1052
1053        assert_eq!(
1054            run(
1055                &create_context(&command_runner, &Default::default(), &Default::default()),
1056                create_simple_config(
1057                    vec![explicit_build(
1058                        vec!["foo".into()],
1059                        Rule::new("cp bar foo".into(), None),
1060                        vec!["bar".into()],
1061                    )],
1062                    &["foo"],
1063                ),
1064                &[],
1065                DEFAULT_OPTIONS,
1066            )
1067            .await,
1068            Err(BuildError::FileNotFound("bar".into()))
1069        );
1070        assert_eq!(command_runner.commands(), Vec::<String>::new());
1071    }
1072
1073    #[tokio::test]
1074    async fn fail_with_missing_order_only_input() {
1075        assert_eq!(
1076            run(
1077                &create_context(
1078                    &Default::default(),
1079                    &Default::default(),
1080                    &Default::default()
1081                ),
1082                create_simple_config(
1083                    vec![Build::new(
1084                        vec!["foo".into()],
1085                        vec![],
1086                        Rule::new("touch foo".into(), None).into(),
1087                        vec![],
1088                        vec!["bar".into()],
1089                        None,
1090                    )],
1091                    &["foo"],
1092                ),
1093                &[],
1094                DEFAULT_OPTIONS,
1095            )
1096            .await,
1097            Err(BuildError::FileNotFound("bar".into()))
1098        );
1099    }
1100
1101    #[tokio::test]
1102    async fn report_source_of_missing_input() {
1103        let context = create_context(
1104            &Default::default(),
1105            &Default::default(),
1106            &Default::default(),
1107        );
1108
1109        context
1110            .database()
1111            .set_output("foo.o", Some("foo.c"))
1112            .await
1113            .unwrap();
1114
1115        assert_eq!(
1116            run(
1117                &context,
1118                create_simple_config(
1119                    vec![explicit_build(
1120                        vec!["foo".into()],
1121                        Rule::new("cc foo.o".into(), None),
1122                        vec!["foo.o".into()],
1123                    )],
1124                    &["foo"],
1125                ),
1126                &[],
1127                DEFAULT_OPTIONS,
1128            )
1129            .await,
1130            Err(BuildError::FileNotFound("foo.c".into()))
1131        );
1132    }
1133
1134    #[tokio::test]
1135    async fn detect_circular_dependency() {
1136        assert_eq!(
1137            run(
1138                &create_context(
1139                    &Default::default(),
1140                    &Default::default(),
1141                    &Default::default()
1142                ),
1143                create_simple_config(
1144                    vec![explicit_build(
1145                        vec!["foo".into()],
1146                        Rule::new("cp foo foo".into(), None),
1147                        vec!["foo".into()],
1148                    )],
1149                    &["foo"],
1150                ),
1151                &[],
1152                DEFAULT_OPTIONS,
1153            )
1154            .await,
1155            Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1156                vec!["foo".into()]
1157            )))
1158        );
1159    }
1160
1161    #[tokio::test]
1162    async fn report_sources_in_circular_dependency() {
1163        let context = create_context(
1164            &Default::default(),
1165            &Default::default(),
1166            &Default::default(),
1167        );
1168
1169        context
1170            .database()
1171            .set_output("foo", Some("baz"))
1172            .await
1173            .unwrap();
1174        context
1175            .database()
1176            .set_output("bar", Some("baz"))
1177            .await
1178            .unwrap();
1179
1180        assert_eq!(
1181            run(
1182                &context,
1183                create_simple_config(
1184                    vec![
1185                        explicit_build(
1186                            vec!["foo".into()],
1187                            Rule::new("cp bar foo".into(), None),
1188                            vec!["bar".into()],
1189                        ),
1190                        explicit_build(
1191                            vec!["bar".into()],
1192                            Rule::new("cp foo bar".into(), None),
1193                            vec!["foo".into()],
1194                        ),
1195                    ],
1196                    &["foo"],
1197                ),
1198                &[],
1199                DEFAULT_OPTIONS,
1200            )
1201            .await,
1202            Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1203                vec!["baz".into()]
1204            )))
1205        );
1206    }
1207
1208    #[tokio::test]
1209    async fn prepare_output_directory() {
1210        let context = create_context(
1211            &Default::default(),
1212            &Default::default(),
1213            &Default::default(),
1214        );
1215
1216        run(
1217            &context,
1218            create_simple_config(
1219                vec![explicit_build(
1220                    vec!["foo/bar".into()],
1221                    Rule::new("touch foo/bar".into(), None),
1222                    vec![],
1223                )],
1224                &["foo/bar"],
1225            ),
1226            &[],
1227            DEFAULT_OPTIONS,
1228        )
1229        .await
1230        .unwrap();
1231
1232        assert!(context.file_system().exists("foo".as_ref()).await.unwrap());
1233    }
1234
1235    #[tokio::test]
1236    async fn record_output_with_source() {
1237        let file_system = FakeFileSystem::default();
1238        let context = create_context(&Default::default(), &Default::default(), &file_system);
1239
1240        file_system.write_file("foo.c", "");
1241
1242        run(
1243            &context,
1244            Config::new(
1245                create_outputs(vec![explicit_build(
1246                    vec!["foo.o".into()],
1247                    Rule::new("cc foo.c".into(), None),
1248                    vec!["foo.c".into()],
1249                )]),
1250                ["foo.o".into()].into_iter().collect(),
1251                [("foo.o".into(), "foo.c".into())].into_iter().collect(),
1252                Default::default(),
1253                None,
1254            )
1255            .into(),
1256            &[],
1257            DEFAULT_OPTIONS,
1258        )
1259        .await
1260        .unwrap();
1261
1262        assert_eq!(context.database().get_outputs().await.unwrap(), ["foo.o"]);
1263        assert_eq!(
1264            context.database().get_source("foo.o").await.unwrap(),
1265            Some("foo.c".into())
1266        );
1267    }
1268
1269    #[tokio::test]
1270    async fn skip_up_to_date_build() {
1271        let command_runner = FakeCommandRunner::default();
1272        let file_system = FakeFileSystem::default();
1273        let context = create_context(&command_runner, &Default::default(), &file_system);
1274        let config = create_simple_config(
1275            vec![explicit_build(
1276                vec!["foo".into()],
1277                Rule::new("cp bar foo".into(), None),
1278                vec!["bar".into()],
1279            )],
1280            &["foo"],
1281        );
1282
1283        file_system.write_file("foo", "");
1284        file_system.write_file("bar", "");
1285
1286        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1287            .await
1288            .unwrap();
1289        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1290
1291        assert_eq!(command_runner.commands(), ["cp bar foo"]);
1292    }
1293
1294    #[tokio::test]
1295    async fn skip_build_on_timestamp_update_of_input() {
1296        let command_runner = FakeCommandRunner::default();
1297        let file_system = FakeFileSystem::default();
1298        let context = create_context(&command_runner, &Default::default(), &file_system);
1299        let config = create_simple_config(
1300            vec![explicit_build(
1301                vec!["foo".into()],
1302                Rule::new("cp bar foo".into(), None),
1303                vec!["bar".into()],
1304            )],
1305            &["foo"],
1306        );
1307
1308        file_system.write_file("foo", "");
1309        file_system.write_file("bar", "");
1310
1311        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1312            .await
1313            .unwrap();
1314
1315        file_system.write_file("bar", "");
1316
1317        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1318
1319        assert_eq!(command_runner.commands(), ["cp bar foo"]);
1320    }
1321
1322    #[tokio::test]
1323    async fn rebuild_on_content_update_of_input() {
1324        let command_runner = FakeCommandRunner::default();
1325        let file_system = FakeFileSystem::default();
1326        let context = create_context(&command_runner, &Default::default(), &file_system);
1327        let config = create_simple_config(
1328            vec![explicit_build(
1329                vec!["foo".into()],
1330                Rule::new("cp bar foo".into(), None),
1331                vec!["bar".into()],
1332            )],
1333            &["foo"],
1334        );
1335
1336        file_system.write_file("foo", "");
1337        file_system.write_file("bar", "");
1338
1339        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1340            .await
1341            .unwrap();
1342
1343        file_system.write_file("bar", "bar");
1344
1345        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1346
1347        assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1348    }
1349
1350    #[tokio::test]
1351    async fn rebuild_on_update_of_phony_input() {
1352        let command_runner = FakeCommandRunner::default();
1353        let file_system = FakeFileSystem::default();
1354        let context = create_context(&command_runner, &Default::default(), &file_system);
1355        let config = create_simple_config(
1356            vec![
1357                explicit_build(
1358                    vec!["foo".into()],
1359                    Rule::new("cp bar foo".into(), None),
1360                    vec!["bar".into()],
1361                ),
1362                Build::new(
1363                    vec!["bar".into()],
1364                    vec![],
1365                    None,
1366                    vec!["baz".into()],
1367                    vec![],
1368                    None,
1369                ),
1370            ],
1371            &["foo"],
1372        );
1373
1374        file_system.write_file("foo", "");
1375        file_system.write_file("baz", "");
1376
1377        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1378            .await
1379            .unwrap();
1380
1381        file_system.write_file("baz", "baz");
1382
1383        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1384
1385        assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1386    }
1387
1388    #[tokio::test]
1389    async fn rebuild_on_update_of_input_of_nested_phony_input() {
1390        let command_runner = FakeCommandRunner::default();
1391        let file_system = FakeFileSystem::default();
1392        let context = create_context(&command_runner, &Default::default(), &file_system);
1393        let config = create_simple_config(
1394            vec![
1395                explicit_build(
1396                    vec!["foo".into()],
1397                    Rule::new("cp bar foo".into(), None),
1398                    vec!["bar".into()],
1399                ),
1400                Build::new(
1401                    vec!["bar".into()],
1402                    vec![],
1403                    None,
1404                    vec!["baz".into()],
1405                    vec![],
1406                    None,
1407                ),
1408                Build::new(
1409                    vec!["baz".into()],
1410                    vec![],
1411                    None,
1412                    vec!["qux".into()],
1413                    vec![],
1414                    None,
1415                ),
1416            ],
1417            &["foo"],
1418        );
1419
1420        file_system.write_file("foo", "");
1421        file_system.write_file("qux", "");
1422
1423        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1424            .await
1425            .unwrap();
1426
1427        file_system.write_file("qux", "qux");
1428
1429        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1430
1431        assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1432    }
1433
1434    #[tokio::test]
1435    async fn rebuild_missing_output() {
1436        let command_runner = FakeCommandRunner::default();
1437        let file_system = FakeFileSystem::default();
1438        let context = create_context(&command_runner, &Default::default(), &file_system);
1439        let config = create_simple_config(
1440            vec![explicit_build(
1441                vec!["foo".into()],
1442                Rule::new("cp bar foo".into(), None),
1443                vec!["bar".into()],
1444            )],
1445            &["foo"],
1446        );
1447
1448        file_system.write_file("bar", "");
1449
1450        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1451            .await
1452            .unwrap();
1453        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1454
1455        assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1456    }
1457
1458    #[tokio::test]
1459    async fn rebuild_missing_implicit_output() {
1460        let command_runner = FakeCommandRunner::default();
1461        let file_system = FakeFileSystem::default();
1462        let context = create_context(&command_runner, &Default::default(), &file_system);
1463        let config = create_simple_config(
1464            vec![Build::new(
1465                vec!["foo".into()],
1466                vec!["baz".into()],
1467                Rule::new("cp bar foo".into(), None).into(),
1468                vec!["bar".into()],
1469                vec![],
1470                None,
1471            )],
1472            &["foo"],
1473        );
1474
1475        file_system.write_file("foo", "");
1476        file_system.write_file("bar", "");
1477
1478        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1479            .await
1480            .unwrap();
1481        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1482
1483        assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1484    }
1485
1486    #[tokio::test]
1487    async fn rebuild_dependent_of_output_written_by_command_of_input() {
1488        let command_runner = FakeCommandRunner::default();
1489        let file_system = FakeFileSystem::default();
1490        let context = create_context(&command_runner, &Default::default(), &file_system);
1491        let config = create_simple_config(
1492            vec![
1493                explicit_build(
1494                    vec!["foo".into()],
1495                    Rule::new("cp bar foo".into(), None),
1496                    vec!["bar".into()],
1497                ),
1498                explicit_build(
1499                    vec!["bar".into()],
1500                    Rule::new("true".into(), None),
1501                    vec!["baz".into()],
1502                ),
1503                explicit_build(
1504                    vec!["baz".into()],
1505                    Rule::new("cp qux bar && touch baz".into(), None),
1506                    vec!["qux".into()],
1507                ),
1508            ],
1509            &["foo"],
1510        );
1511
1512        file_system.write_file("foo", "");
1513        file_system.write_file("bar", "");
1514        file_system.write_file("baz", "");
1515        file_system.write_file("qux", "");
1516
1517        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1518            .await
1519            .unwrap();
1520
1521        file_system.write_file("qux", "qux");
1522
1523        let mut future = pin!(run(&context, config, &[], DEFAULT_OPTIONS));
1524
1525        assert!(poll!(&mut future).is_pending());
1526
1527        // Let the runtime run the builds until the command of the input suspends.
1528        yield_now().await;
1529
1530        assert_eq!(
1531            command_runner.commands(),
1532            [
1533                "cp qux bar && touch baz",
1534                "true",
1535                "cp bar foo",
1536                "cp qux bar && touch baz"
1537            ]
1538        );
1539
1540        file_system.write_file("bar", "qux");
1541        file_system.write_file("baz", "");
1542        future.await.unwrap();
1543
1544        assert_eq!(
1545            command_runner.commands(),
1546            [
1547                "cp qux bar && touch baz",
1548                "true",
1549                "cp bar foo",
1550                "cp qux bar && touch baz",
1551                "cp bar foo"
1552            ]
1553        );
1554    }
1555
1556    #[tokio::test]
1557    async fn rerun_failed_build() {
1558        let command_runner =
1559            FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1560        let file_system = FakeFileSystem::default();
1561        let context = create_context(&command_runner, &Default::default(), &file_system);
1562        let config = create_simple_config(
1563            vec![explicit_build(
1564                vec!["foo".into()],
1565                Rule::new("exit 1".into(), None),
1566                vec![],
1567            )],
1568            &["foo"],
1569        );
1570
1571        file_system.write_file("foo", "");
1572
1573        assert_eq!(
1574            run(&context, config.clone(), &[], DEFAULT_OPTIONS).await,
1575            Err(BuildError::Build)
1576        );
1577        assert_eq!(
1578            run(&context, config, &[], DEFAULT_OPTIONS).await,
1579            Err(BuildError::Build)
1580        );
1581        assert_eq!(command_runner.commands(), ["exit 1", "exit 1"]);
1582    }
1583
1584    #[tokio::test]
1585    async fn invalidate_file_cache_of_outputs_after_command() {
1586        let command_runner = FakeCommandRunner::default();
1587        let file_system = FakeFileSystem::default();
1588        let context = create_build_run_context(&command_runner, &file_system, vec![]);
1589        let build = Build::new(
1590            vec!["foo".into()],
1591            vec!["bar".into()],
1592            Rule::new("touch foo bar".into(), None).into(),
1593            vec![],
1594            vec![],
1595            None,
1596        )
1597        .into();
1598        let mut future = pin!(run_build(context.clone(), &build));
1599
1600        file_system.write_file("foo", "1");
1601        file_system.write_file("bar", "1");
1602
1603        assert!(poll!(&mut future).is_pending());
1604
1605        // Let the runtime run the build until its command suspends.
1606        yield_now().await;
1607
1608        assert_eq!(command_runner.commands(), ["touch foo bar"]);
1609
1610        let foo_hash = context.file_cache().content_hash(&"foo".into()).await;
1611        let bar_hash = context.file_cache().content_hash(&"bar".into()).await;
1612
1613        file_system.write_file("foo", "2");
1614        file_system.write_file("bar", "2");
1615        future.await.unwrap();
1616
1617        assert_ne!(
1618            context.file_cache().content_hash(&"foo".into()).await,
1619            foo_hash
1620        );
1621        assert_ne!(
1622            context.file_cache().content_hash(&"bar".into()).await,
1623            bar_hash
1624        );
1625    }
1626
1627    #[tokio::test]
1628    async fn invalidate_file_cache_of_output_after_failed_command() {
1629        let command_runner =
1630            FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1631        let file_system = FakeFileSystem::default();
1632        let context = create_build_run_context(&command_runner, &file_system, vec![]);
1633        let build =
1634            explicit_build(vec!["foo".into()], Rule::new("exit 1".into(), None), vec![]).into();
1635        let mut future = pin!(run_build(context.clone(), &build));
1636
1637        file_system.write_file("foo", "1");
1638
1639        assert!(poll!(&mut future).is_pending());
1640
1641        // Let the runtime run the build until its command suspends.
1642        yield_now().await;
1643
1644        assert_eq!(command_runner.commands(), ["exit 1"]);
1645
1646        let hash = context.file_cache().content_hash(&"foo".into()).await;
1647
1648        file_system.write_file("foo", "2");
1649
1650        assert_eq!(future.await, Err(BuildError::Build));
1651        assert_ne!(context.file_cache().content_hash(&"foo".into()).await, hash);
1652    }
1653
1654    #[tokio::test]
1655    async fn invalidate_file_cache_of_phony_output_after_inputs() {
1656        let command_runner = FakeCommandRunner::default();
1657        let file_system = FakeFileSystem::default();
1658        let build = Build::new(
1659            vec!["foo".into()],
1660            vec![],
1661            None,
1662            vec!["bar".into()],
1663            vec![],
1664            None,
1665        );
1666        let context = create_build_run_context(
1667            &command_runner,
1668            &file_system,
1669            vec![
1670                build.clone(),
1671                explicit_build(
1672                    vec!["bar".into()],
1673                    Rule::new("touch bar".into(), None),
1674                    vec![],
1675                ),
1676            ],
1677        );
1678        let build = build.into();
1679        let mut future = pin!(run_build(context.clone(), &build));
1680
1681        file_system.write_file("foo", "1");
1682        file_system.write_file("bar", "");
1683
1684        assert!(poll!(&mut future).is_pending());
1685
1686        // Let the runtime run the builds until the command of the input suspends.
1687        yield_now().await;
1688
1689        assert_eq!(command_runner.commands(), ["touch bar"]);
1690
1691        let hash = context.file_cache().content_hash(&"foo".into()).await;
1692
1693        file_system.write_file("foo", "2");
1694        future.await.unwrap();
1695
1696        assert_ne!(context.file_cache().content_hash(&"foo".into()).await, hash);
1697    }
1698
1699    #[tokio::test]
1700    async fn keep_file_cache_of_other_file_after_command() {
1701        let command_runner = FakeCommandRunner::default();
1702        let file_system = FakeFileSystem::default();
1703        let context = create_build_run_context(&command_runner, &file_system, vec![]);
1704
1705        file_system.write_file("bar", "1");
1706
1707        let hash = context.file_cache().content_hash(&"bar".into()).await;
1708
1709        file_system.write_file("bar", "2");
1710        run_build(
1711            context.clone(),
1712            &explicit_build(
1713                vec!["foo".into()],
1714                Rule::new("touch foo".into(), None),
1715                vec![],
1716            )
1717            .into(),
1718        )
1719        .await
1720        .unwrap();
1721
1722        assert_eq!(command_runner.commands(), ["touch foo"]);
1723        assert_eq!(context.file_cache().content_hash(&"bar".into()).await, hash);
1724    }
1725
1726    #[tokio::test]
1727    async fn query_output_while_building_input() {
1728        let file_system = FakeFileSystem::default();
1729
1730        file_system.write_file("foo", "");
1731        file_system.write_file("bar", "");
1732        file_system.write_file("baz", "");
1733
1734        run(
1735            &create_context(&Default::default(), &Default::default(), &file_system),
1736            create_simple_config(
1737                vec![
1738                    explicit_build(
1739                        vec!["foo".into()],
1740                        Rule::new("cp bar foo".into(), None),
1741                        vec!["bar".into()],
1742                    ),
1743                    explicit_build(
1744                        vec!["bar".into()],
1745                        Rule::new("cp baz bar".into(), None),
1746                        vec!["baz".into()],
1747                    ),
1748                ],
1749                &["foo"],
1750            ),
1751            &[],
1752            DEFAULT_OPTIONS,
1753        )
1754        .await
1755        .unwrap();
1756
1757        assert_eq!(file_system.metadata_requests().first(), Some(&"foo".into()));
1758    }
1759
1760    #[tokio::test]
1761    async fn query_output_once_for_up_to_date_build() {
1762        let file_system = FakeFileSystem::default();
1763        let context = create_context(&Default::default(), &Default::default(), &file_system);
1764        let config = create_simple_config(
1765            vec![
1766                explicit_build(
1767                    vec!["foo".into()],
1768                    Rule::new("cp bar foo".into(), None),
1769                    vec!["bar".into()],
1770                ),
1771                explicit_build(
1772                    vec!["bar".into()],
1773                    Rule::new("cp baz bar".into(), None),
1774                    vec!["baz".into()],
1775                ),
1776            ],
1777            &["foo"],
1778        );
1779
1780        file_system.write_file("foo", "");
1781        file_system.write_file("bar", "");
1782        file_system.write_file("baz", "");
1783
1784        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1785            .await
1786            .unwrap();
1787
1788        let count = count_metadata_requests(&file_system, "bar");
1789
1790        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1791
1792        assert_eq!(count_metadata_requests(&file_system, "bar"), count + 1);
1793    }
1794
1795    #[tokio::test]
1796    async fn query_implicit_output_once_for_up_to_date_build() {
1797        let file_system = FakeFileSystem::default();
1798        let context = create_context(&Default::default(), &Default::default(), &file_system);
1799        let config = create_simple_config(
1800            vec![
1801                explicit_build(
1802                    vec!["foo".into()],
1803                    Rule::new("cp qux foo".into(), None),
1804                    vec!["qux".into()],
1805                ),
1806                Build::new(
1807                    vec!["bar".into()],
1808                    vec!["qux".into()],
1809                    Rule::new("cp baz bar".into(), None).into(),
1810                    vec!["baz".into()],
1811                    vec![],
1812                    None,
1813                ),
1814            ],
1815            &["foo"],
1816        );
1817
1818        file_system.write_file("foo", "");
1819        file_system.write_file("bar", "");
1820        file_system.write_file("baz", "");
1821        file_system.write_file("qux", "");
1822
1823        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1824            .await
1825            .unwrap();
1826
1827        let count = count_metadata_requests(&file_system, "qux");
1828
1829        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1830
1831        assert_eq!(count_metadata_requests(&file_system, "qux"), count + 1);
1832    }
1833
1834    #[tokio::test]
1835    async fn query_output_again_on_timestamp_update_of_input() {
1836        let file_system = FakeFileSystem::default();
1837        let context = create_context(&Default::default(), &Default::default(), &file_system);
1838        let config = create_simple_config(
1839            vec![
1840                explicit_build(
1841                    vec!["foo".into()],
1842                    Rule::new("cp bar foo".into(), None),
1843                    vec!["bar".into()],
1844                ),
1845                explicit_build(
1846                    vec!["bar".into()],
1847                    Rule::new("cp baz bar".into(), None),
1848                    vec!["baz".into()],
1849                ),
1850            ],
1851            &["foo"],
1852        );
1853
1854        file_system.write_file("foo", "");
1855        file_system.write_file("bar", "");
1856        file_system.write_file("baz", "");
1857
1858        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1859            .await
1860            .unwrap();
1861
1862        file_system.write_file("baz", "");
1863
1864        let count = count_metadata_requests(&file_system, "bar");
1865
1866        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1867
1868        assert_eq!(count_metadata_requests(&file_system, "bar"), count + 2);
1869    }
1870
1871    #[tokio::test]
1872    async fn query_output_again_after_command() {
1873        let file_system = FakeFileSystem::default();
1874
1875        file_system.write_file("foo", "");
1876        file_system.write_file("bar", "");
1877        file_system.write_file("baz", "");
1878
1879        run(
1880            &create_context(&Default::default(), &Default::default(), &file_system),
1881            create_simple_config(
1882                vec![
1883                    explicit_build(
1884                        vec!["foo".into()],
1885                        Rule::new("cp bar foo".into(), None),
1886                        vec!["bar".into()],
1887                    ),
1888                    explicit_build(
1889                        vec!["bar".into()],
1890                        Rule::new("cp baz bar".into(), None),
1891                        vec!["baz".into()],
1892                    ),
1893                ],
1894                &["foo"],
1895            ),
1896            &[],
1897            DEFAULT_OPTIONS,
1898        )
1899        .await
1900        .unwrap();
1901
1902        assert_eq!(count_metadata_requests(&file_system, "bar"), 2);
1903    }
1904
1905    #[tokio::test]
1906    async fn query_output_again_for_up_to_date_phony_build() {
1907        let file_system = FakeFileSystem::default();
1908        let context = create_context(&Default::default(), &Default::default(), &file_system);
1909        let config = create_simple_config(
1910            vec![
1911                explicit_build(
1912                    vec!["foo.o".into()],
1913                    Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
1914                        HeaderDependency::Make {
1915                            path: "foo.d".into(),
1916                        },
1917                    )),
1918                    vec!["foo.c".into()],
1919                ),
1920                Build::new(
1921                    vec!["foo.h".into()],
1922                    vec![],
1923                    None,
1924                    vec!["foo.h.in".into()],
1925                    vec![],
1926                    None,
1927                ),
1928            ],
1929            &["foo.o", "foo.h"],
1930        );
1931
1932        file_system.write_file("foo.o", "");
1933        file_system.write_file("foo.c", "");
1934        file_system.write_file("foo.h", "");
1935        file_system.write_file("foo.h.in", "");
1936        file_system.write_file("foo.d", "foo.o: foo.h\n");
1937
1938        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1939            .await
1940            .unwrap();
1941
1942        let count = count_metadata_requests(&file_system, "foo.h");
1943
1944        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1945
1946        assert_eq!(count_metadata_requests(&file_system, "foo.h"), count + 2);
1947    }
1948
1949    #[tokio::test]
1950    async fn read_no_input_of_up_to_date_phony_build() {
1951        let file_system = FakeFileSystem::default();
1952        let context = create_context(&Default::default(), &Default::default(), &file_system);
1953        let config = create_simple_config(
1954            vec![Build::new(
1955                vec!["foo".into()],
1956                vec![],
1957                None,
1958                vec!["bar".into()],
1959                vec![],
1960                None,
1961            )],
1962            &["foo"],
1963        );
1964
1965        file_system.write_file("bar", "");
1966
1967        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1968            .await
1969            .unwrap();
1970
1971        let count = file_system.read_requests().len();
1972
1973        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1974
1975        assert_eq!(file_system.read_requests()[count..], Vec::<&Path>::new());
1976    }
1977
1978    #[tokio::test]
1979    async fn read_no_input_on_timestamp_update_of_input_of_phony_input() {
1980        let file_system = FakeFileSystem::default();
1981        let context = create_context(&Default::default(), &Default::default(), &file_system);
1982        let config = create_simple_config(
1983            vec![
1984                explicit_build(
1985                    vec!["foo".into()],
1986                    Rule::new("cp bar foo".into(), None),
1987                    vec!["bar".into(), "qux".into()],
1988                ),
1989                Build::new(
1990                    vec!["bar".into()],
1991                    vec![],
1992                    None,
1993                    vec!["baz".into()],
1994                    vec![],
1995                    None,
1996                ),
1997            ],
1998            &["foo"],
1999        );
2000
2001        file_system.write_file("foo", "");
2002        file_system.write_file("baz", "");
2003        file_system.write_file("qux", "");
2004
2005        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
2006            .await
2007            .unwrap();
2008
2009        file_system.write_file("baz", "");
2010
2011        let count = file_system.read_requests().len();
2012
2013        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
2014
2015        assert_eq!(file_system.read_requests()[count..], [Path::new("baz")]);
2016    }
2017
2018    #[tokio::test]
2019    async fn fail_with_output_metadata_error() {
2020        let command_runner = FakeCommandRunner::default();
2021        let file_system = FakeFileSystem::default();
2022
2023        file_system.write_file("bar", "");
2024        file_system.fail_metadata("foo");
2025
2026        assert_eq!(
2027            run(
2028                &create_context(&command_runner, &Default::default(), &file_system),
2029                create_simple_config(
2030                    vec![explicit_build(
2031                        vec!["foo".into()],
2032                        Rule::new("cp bar foo".into(), None),
2033                        vec!["bar".into()],
2034                    )],
2035                    &["foo"],
2036                ),
2037                &[],
2038                DEFAULT_OPTIONS,
2039            )
2040            .await,
2041            Err(BuildError::File(FileError::new("metadata failure")))
2042        );
2043        assert_eq!(command_runner.commands(), Vec::<String>::new());
2044    }
2045
2046    #[tokio::test]
2047    async fn write_command_output() {
2048        let console = FakeConsole::default();
2049
2050        run(
2051            &create_context(
2052                &FakeCommandRunner::new(
2053                    [(
2054                        "touch foo".into(),
2055                        Output {
2056                            status: ExitStatus::default(),
2057                            stdout: b"bar\n".into(),
2058                            stderr: b"baz\n".into(),
2059                        },
2060                    )]
2061                    .into_iter()
2062                    .collect(),
2063                ),
2064                &console,
2065                &Default::default(),
2066            ),
2067            create_simple_config(
2068                vec![explicit_build(
2069                    vec!["foo".into()],
2070                    Rule::new("touch foo".into(), Some("build foo".into())),
2071                    vec![],
2072                )],
2073                &["foo"],
2074            ),
2075            &[],
2076            DEFAULT_OPTIONS,
2077        )
2078        .await
2079        .unwrap();
2080
2081        assert_eq!(console.stdout(), "build foo\nbar\n");
2082        assert_eq!(console.stderr(), "baz\n");
2083    }
2084
2085    #[tokio::test]
2086    async fn write_debug_log_of_failed_build() {
2087        let console = FakeConsole::default();
2088
2089        assert_eq!(
2090            run(
2091                &create_context(
2092                    &FakeCommandRunner::new(
2093                        [("exit 1".into(), failed_output())].into_iter().collect(),
2094                    ),
2095                    &console,
2096                    &Default::default(),
2097                ),
2098                create_simple_config(
2099                    vec![explicit_build(
2100                        vec!["foo".into()],
2101                        Rule::new("exit 1".into(), None),
2102                        vec![],
2103                    )],
2104                    &["foo"],
2105                ),
2106                &[],
2107                RunOptions {
2108                    debug: true,
2109                    dry_run: false,
2110                    profile: false,
2111                },
2112            )
2113            .await,
2114            Err(BuildError::Build)
2115        );
2116        assert_eq!(
2117            console.stderr(),
2118            "turtle: command: exit 1\nturtle: exit status: 1\n"
2119        );
2120    }
2121
2122    #[tokio::test]
2123    async fn write_profile_log() {
2124        let console = FakeConsole::default();
2125
2126        run(
2127            &create_context(&Default::default(), &console, &Default::default()),
2128            create_simple_config(
2129                vec![explicit_build(
2130                    vec!["foo".into()],
2131                    Rule::new("touch foo".into(), None),
2132                    vec![],
2133                )],
2134                &["foo"],
2135            ),
2136            &[],
2137            RunOptions {
2138                debug: false,
2139                dry_run: false,
2140                profile: true,
2141            },
2142        )
2143        .await
2144        .unwrap();
2145
2146        assert!(
2147            Regex::new(r"^turtle: duration: \d+ ms\n$")
2148                .unwrap()
2149                .is_match(&console.stderr())
2150        );
2151    }
2152
2153    #[tokio::test]
2154    async fn rebuild_on_update_of_header_dependency() {
2155        let command_runner = FakeCommandRunner::default();
2156        let file_system = FakeFileSystem::default();
2157        let context = create_context(&command_runner, &Default::default(), &file_system);
2158        let config = create_simple_config(
2159            vec![explicit_build(
2160                vec!["foo.o".into()],
2161                Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
2162                    HeaderDependency::Make {
2163                        path: "foo.d".into(),
2164                    },
2165                )),
2166                vec!["foo.c".into()],
2167            )],
2168            &["foo.o"],
2169        );
2170
2171        file_system.write_file("foo.o", "");
2172        file_system.write_file("foo.c", "");
2173        file_system.write_file("foo.h", "");
2174        file_system.write_file("foo.d", "foo.o: foo.h\n");
2175
2176        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
2177            .await
2178            .unwrap();
2179        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
2180            .await
2181            .unwrap();
2182
2183        file_system.write_file("foo.h", "foo");
2184
2185        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
2186
2187        assert_eq!(command_runner.commands(), ["cc foo.c", "cc foo.c"]);
2188    }
2189
2190    #[tokio::test]
2191    async fn read_and_remove_gcc_depfile() {
2192        let file_system = FakeFileSystem::default();
2193        let context = create_context(&Default::default(), &Default::default(), &file_system);
2194        let build = explicit_build(
2195            vec!["foo.o".into()],
2196            Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
2197                HeaderDependency::Gcc {
2198                    path: "foo.d".into(),
2199                },
2200            )),
2201            vec!["foo.c".into()],
2202        );
2203
2204        file_system.write_file("foo.c", "");
2205        file_system.write_file("foo.h", "");
2206        file_system.write_file("foo.d", "foo.o: foo.h\n");
2207
2208        run(
2209            &context,
2210            create_simple_config(vec![build.clone()], &["foo.o"]),
2211            &[],
2212            DEFAULT_OPTIONS,
2213        )
2214        .await
2215        .unwrap();
2216
2217        assert_eq!(
2218            context
2219                .database()
2220                .get_header_inputs(build.id())
2221                .await
2222                .unwrap(),
2223            ["foo.h".into()]
2224        );
2225        assert!(
2226            !context
2227                .file_system()
2228                .exists("foo.d".as_ref())
2229                .await
2230                .unwrap()
2231        );
2232    }
2233
2234    #[tokio::test]
2235    async fn read_msvc_header_dependencies() {
2236        let console = FakeConsole::default();
2237        let file_system = FakeFileSystem::default();
2238        let context = create_context(
2239            &FakeCommandRunner::new(
2240                [(
2241                    "cl foo.c".into(),
2242                    Output {
2243                        status: ExitStatus::default(),
2244                        stdout: b"Note: including file: foo.h\nfoo.c\n".into(),
2245                        stderr: vec![],
2246                    },
2247                )]
2248                .into_iter()
2249                .collect(),
2250            ),
2251            &console,
2252            &file_system,
2253        );
2254        let build = explicit_build(
2255            vec!["foo.obj".into()],
2256            Rule::new("cl foo.c".into(), None).with_header_dependency(Some(
2257                HeaderDependency::Msvc {
2258                    prefix: "Note: including file: ".into(),
2259                },
2260            )),
2261            vec!["foo.c".into()],
2262        );
2263
2264        file_system.write_file("foo.c", "");
2265        file_system.write_file("foo.h", "");
2266
2267        run(
2268            &context,
2269            create_simple_config(vec![build.clone()], &["foo.obj"]),
2270            &[],
2271            DEFAULT_OPTIONS,
2272        )
2273        .await
2274        .unwrap();
2275
2276        assert_eq!(
2277            context
2278                .database()
2279                .get_header_inputs(build.id())
2280                .await
2281                .unwrap(),
2282            ["foo.h".into()]
2283        );
2284        assert_eq!(console.stdout(), "foo.c\n");
2285    }
2286
2287    #[tokio::test]
2288    async fn intern_header_dependencies() {
2289        let file_system = FakeFileSystem::default();
2290        let context = create_context(&Default::default(), &Default::default(), &file_system);
2291        let build = explicit_build(
2292            vec!["foo.o".into()],
2293            Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
2294                HeaderDependency::Gcc {
2295                    path: "foo.d".into(),
2296                },
2297            )),
2298            vec!["foo.c".into()],
2299        );
2300
2301        file_system.write_file("foo.c", "");
2302        file_system.write_file("foo.h", "");
2303        file_system.write_file("foo.d", "foo.o: ./foo.h\n");
2304
2305        run(
2306            &context,
2307            create_simple_config(vec![build.clone()], &["foo.o"]),
2308            &[],
2309            DEFAULT_OPTIONS,
2310        )
2311        .await
2312        .unwrap();
2313
2314        assert!(Arc::ptr_eq(
2315            &context
2316                .database()
2317                .get_header_inputs(build.id())
2318                .await
2319                .unwrap()[0],
2320            &context.path_pool().intern("foo.h")
2321        ));
2322    }
2323
2324    #[tokio::test]
2325    async fn build_generated_header_dependency() {
2326        let command_runner = FakeCommandRunner::default();
2327        let file_system = FakeFileSystem::default();
2328        let context = create_context(&command_runner, &Default::default(), &file_system);
2329        let build = explicit_build(
2330            vec!["foo.o".into()],
2331            Rule::new("cc foo.c".into(), None),
2332            vec!["foo.c".into()],
2333        );
2334
2335        context
2336            .database()
2337            .set_header_inputs(build.id(), &["foo.h".into()])
2338            .await
2339            .unwrap();
2340        file_system.write_file("foo.c", "");
2341        file_system.write_file("foo.h", "");
2342
2343        run(
2344            &context,
2345            create_simple_config(
2346                vec![
2347                    build,
2348                    explicit_build(
2349                        vec!["foo.h".into()],
2350                        Rule::new("touch foo.h".into(), None),
2351                        vec![],
2352                    ),
2353                ],
2354                &["foo.o"],
2355            ),
2356            &[],
2357            DEFAULT_OPTIONS,
2358        )
2359        .await
2360        .unwrap();
2361
2362        assert_eq!(command_runner.commands(), ["touch foo.h", "cc foo.c"]);
2363    }
2364
2365    #[tokio::test]
2366    async fn ignore_missing_header_dependency() {
2367        let file_system = FakeFileSystem::default();
2368        let context = create_context(&Default::default(), &Default::default(), &file_system);
2369        let build = explicit_build(
2370            vec!["foo.o".into()],
2371            Rule::new("cc foo.c".into(), None),
2372            vec!["foo.c".into()],
2373        );
2374
2375        context
2376            .database()
2377            .set_header_inputs(build.id(), &["foo.h".into()])
2378            .await
2379            .unwrap();
2380        file_system.write_file("foo.c", "");
2381
2382        run(
2383            &context,
2384            create_simple_config(vec![build], &["foo.o"]),
2385            &[],
2386            DEFAULT_OPTIONS,
2387        )
2388        .await
2389        .unwrap();
2390    }
2391
2392    #[tokio::test]
2393    async fn read_header_dependencies_once() {
2394        let database = FakeDatabase::default();
2395        let file_system = FakeFileSystem::default();
2396        let context = Arc::new(Context::new(
2397            FakeCommandRunner::default(),
2398            Mutex::new(FakeConsole::default()).into(),
2399            database.clone(),
2400            file_system.clone(),
2401            Default::default(),
2402        ));
2403        let build = explicit_build(
2404            vec!["foo.o".into()],
2405            Rule::new("cc foo.c".into(), None),
2406            vec!["foo.c".into()],
2407        );
2408
2409        context
2410            .database()
2411            .set_header_inputs(build.id(), &["foo.h".into()])
2412            .await
2413            .unwrap();
2414        file_system.write_file("foo.c", "");
2415        file_system.write_file("foo.h", "");
2416
2417        run(
2418            &context,
2419            create_simple_config(vec![build.clone()], &["foo.o"]),
2420            &[],
2421            DEFAULT_OPTIONS,
2422        )
2423        .await
2424        .unwrap();
2425
2426        assert_eq!(database.header_dependency_requests(), [build.id()]);
2427    }
2428
2429    #[tokio::test]
2430    async fn detect_circular_header_dependency() {
2431        let context = create_context(
2432            &Default::default(),
2433            &Default::default(),
2434            &Default::default(),
2435        );
2436        let build = explicit_build(
2437            vec!["foo".into()],
2438            Rule::new("touch foo".into(), None),
2439            vec![],
2440        );
2441
2442        context
2443            .database()
2444            .set_header_inputs(build.id(), &["foo".into()])
2445            .await
2446            .unwrap();
2447
2448        assert_eq!(
2449            run(
2450                &context,
2451                create_simple_config(vec![build], &["foo"]),
2452                &[],
2453                DEFAULT_OPTIONS,
2454            )
2455            .await,
2456            Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
2457                vec!["foo".into()]
2458            )))
2459        );
2460    }
2461
2462    #[tokio::test]
2463    async fn build_dynamic_input() {
2464        let command_runner = FakeCommandRunner::default();
2465        let file_system = FakeFileSystem::default();
2466
2467        file_system.write_file(
2468            "foo.dd",
2469            "ninja_dyndep_version = 1\nbuild foo: dyndep | bar\n",
2470        );
2471        file_system.write_file("bar", "");
2472
2473        run(
2474            &create_context(&command_runner, &Default::default(), &file_system),
2475            create_simple_config(
2476                vec![
2477                    Build::new(
2478                        vec!["foo".into()],
2479                        vec![],
2480                        Rule::new("touch foo".into(), None).into(),
2481                        vec![],
2482                        vec!["foo.dd".into()],
2483                        Some("foo.dd".into()),
2484                    ),
2485                    explicit_build(
2486                        vec!["foo.dd".into()],
2487                        Rule::new("touch foo.dd".into(), None),
2488                        vec![],
2489                    ),
2490                    explicit_build(
2491                        vec!["bar".into()],
2492                        Rule::new("touch bar".into(), None),
2493                        vec![],
2494                    ),
2495                ],
2496                &["foo"],
2497            ),
2498            &[],
2499            DEFAULT_OPTIONS,
2500        )
2501        .await
2502        .unwrap();
2503
2504        assert_eq!(
2505            command_runner.commands(),
2506            ["touch foo.dd", "touch bar", "touch foo"]
2507        );
2508    }
2509
2510    #[tokio::test]
2511    async fn build_dynamic_input_declared_for_implicit_output() {
2512        let command_runner = FakeCommandRunner::default();
2513        let file_system = FakeFileSystem::default();
2514
2515        file_system.write_file(
2516            "foo.dd",
2517            "ninja_dyndep_version = 1\nbuild baz: dyndep | bar\n",
2518        );
2519        file_system.write_file("bar", "");
2520
2521        run(
2522            &create_context(&command_runner, &Default::default(), &file_system),
2523            create_simple_config(
2524                vec![
2525                    Build::new(
2526                        vec!["foo".into()],
2527                        vec!["baz".into()],
2528                        Rule::new("touch foo".into(), None).into(),
2529                        vec![],
2530                        vec!["foo.dd".into()],
2531                        Some("foo.dd".into()),
2532                    ),
2533                    explicit_build(
2534                        vec!["foo.dd".into()],
2535                        Rule::new("touch foo.dd".into(), None),
2536                        vec![],
2537                    ),
2538                    explicit_build(
2539                        vec!["bar".into()],
2540                        Rule::new("touch bar".into(), None),
2541                        vec![],
2542                    ),
2543                ],
2544                &["foo"],
2545            ),
2546            &[],
2547            DEFAULT_OPTIONS,
2548        )
2549        .await
2550        .unwrap();
2551
2552        assert_eq!(
2553            command_runner.commands(),
2554            ["touch foo.dd", "touch bar", "touch foo"]
2555        );
2556    }
2557
2558    #[tokio::test]
2559    async fn build_dynamic_inputs_of_builds_sharing_dynamic_module() {
2560        let command_runner = FakeCommandRunner::default();
2561        let file_system = FakeFileSystem::default();
2562
2563        file_system.write_file(
2564            "foo.dd",
2565            "ninja_dyndep_version = 1\nbuild foo: dyndep | baz\nbuild bar: dyndep | qux\n",
2566        );
2567        file_system.write_file("foo", "");
2568        file_system.write_file("baz", "");
2569        file_system.write_file("qux", "");
2570
2571        run(
2572            &create_context(&command_runner, &Default::default(), &file_system),
2573            create_simple_config(
2574                vec![
2575                    Build::new(
2576                        vec!["foo".into()],
2577                        vec![],
2578                        Rule::new("touch foo".into(), None).into(),
2579                        vec![],
2580                        vec!["foo.dd".into()],
2581                        Some("foo.dd".into()),
2582                    ),
2583                    Build::new(
2584                        vec!["bar".into()],
2585                        vec![],
2586                        Rule::new("touch bar".into(), None).into(),
2587                        vec!["foo".into()],
2588                        vec!["foo.dd".into()],
2589                        Some("foo.dd".into()),
2590                    ),
2591                    explicit_build(
2592                        vec!["baz".into()],
2593                        Rule::new("touch baz".into(), None),
2594                        vec![],
2595                    ),
2596                    explicit_build(
2597                        vec!["qux".into()],
2598                        Rule::new("touch qux".into(), None),
2599                        vec![],
2600                    ),
2601                ],
2602                &["bar"],
2603            ),
2604            &[],
2605            DEFAULT_OPTIONS,
2606        )
2607        .await
2608        .unwrap();
2609
2610        assert_eq!(
2611            command_runner.commands(),
2612            ["touch baz", "touch foo", "touch qux", "touch bar"]
2613        );
2614    }
2615
2616    #[tokio::test]
2617    async fn read_dynamic_module_once() {
2618        let file_system = FakeFileSystem::default();
2619
2620        file_system.write_file(
2621            "foo.dd",
2622            "ninja_dyndep_version = 1\nbuild foo: dyndep\nbuild bar: dyndep\n",
2623        );
2624
2625        run(
2626            &create_context(&Default::default(), &Default::default(), &file_system),
2627            create_simple_config(
2628                ["foo", "bar"]
2629                    .into_iter()
2630                    .map(|output| {
2631                        Build::new(
2632                            vec![output.into()],
2633                            vec![],
2634                            Rule::new(format!("touch {output}"), None).into(),
2635                            vec![],
2636                            vec!["foo.dd".into()],
2637                            Some("foo.dd".into()),
2638                        )
2639                    })
2640                    .collect(),
2641                &["foo", "bar"],
2642            ),
2643            &[],
2644            DEFAULT_OPTIONS,
2645        )
2646        .await
2647        .unwrap();
2648
2649        assert_eq!(file_system.read_requests(), [Path::new("foo.dd")]);
2650    }
2651
2652    #[tokio::test]
2653    async fn intern_dynamic_input() {
2654        let file_system = FakeFileSystem::default();
2655        let config = create_simple_config(
2656            vec![Build::new(
2657                vec!["foo".into()],
2658                vec![],
2659                Rule::new("touch foo".into(), None).into(),
2660                vec![],
2661                vec!["foo.dd".into()],
2662                Some("foo.dd".into()),
2663            )],
2664            &[],
2665        );
2666        let context = RunContext::new(
2667            create_context(&Default::default(), &Default::default(), &file_system),
2668            config.clone(),
2669            BuildGraph::new(config.outputs()),
2670            Default::default(),
2671            DEFAULT_OPTIONS,
2672        );
2673
2674        file_system.write_file(
2675            "foo.dd",
2676            "ninja_dyndep_version = 1\nbuild foo: dyndep | bar\n",
2677        );
2678
2679        assert!(Arc::ptr_eq(
2680            &load_dynamic_config(&context, "foo.dd")
2681                .await
2682                .unwrap()
2683                .outputs()["foo"]
2684                .inputs()[0],
2685            &context.build().path_pool().intern("bar")
2686        ));
2687    }
2688
2689    #[tokio::test]
2690    async fn fail_with_missing_dynamic_dependency() {
2691        let file_system = FakeFileSystem::default();
2692        let build = Build::new(
2693            vec!["foo".into()],
2694            vec![],
2695            Rule::new("touch foo".into(), None).into(),
2696            vec![],
2697            vec![],
2698            Some("foo.dd".into()),
2699        );
2700
2701        file_system.write_file("foo.dd", "ninja_dyndep_version = 1\n");
2702
2703        assert_eq!(
2704            run(
2705                &create_context(&Default::default(), &Default::default(), &file_system),
2706                create_simple_config(vec![build.clone()], &["foo"]),
2707                &[],
2708                DEFAULT_OPTIONS,
2709            )
2710            .await,
2711            Err(BuildError::DynamicDependencyNotFound(build.into()))
2712        );
2713    }
2714
2715    #[tokio::test]
2716    async fn fail_with_unknown_output_in_dynamic_dependency() {
2717        let file_system = FakeFileSystem::default();
2718
2719        file_system.write_file(
2720            "foo.dd",
2721            "ninja_dyndep_version = 1\nbuild bar: dyndep | baz\n",
2722        );
2723
2724        assert_eq!(
2725            run(
2726                &create_context(&Default::default(), &Default::default(), &file_system),
2727                create_simple_config(
2728                    vec![Build::new(
2729                        vec!["foo".into()],
2730                        vec![],
2731                        Rule::new("touch foo".into(), None).into(),
2732                        vec![],
2733                        vec![],
2734                        Some("foo.dd".into()),
2735                    )],
2736                    &["foo"],
2737                ),
2738                &[],
2739                DEFAULT_OPTIONS,
2740            )
2741            .await,
2742            Err(BuildError::BuildGraph(BuildGraphError::OutputNotFound(
2743                "bar".into()
2744            )))
2745        );
2746    }
2747
2748    #[tokio::test]
2749    async fn detect_circular_dynamic_dependency() {
2750        let file_system = FakeFileSystem::default();
2751
2752        file_system.write_file(
2753            "foo.dd",
2754            "ninja_dyndep_version = 1\nbuild foo: dyndep | foo\n",
2755        );
2756
2757        assert_eq!(
2758            run(
2759                &create_context(&Default::default(), &Default::default(), &file_system),
2760                create_simple_config(
2761                    vec![Build::new(
2762                        vec!["foo".into()],
2763                        vec![],
2764                        Rule::new("touch foo".into(), None).into(),
2765                        vec![],
2766                        vec![],
2767                        Some("foo.dd".into()),
2768                    )],
2769                    &["foo"],
2770                ),
2771                &[],
2772                DEFAULT_OPTIONS,
2773            )
2774            .await,
2775            Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
2776                vec!["foo".into()]
2777            )))
2778        );
2779    }
2780
2781    #[tokio::test]
2782    async fn run_builds_concurrently() {
2783        let command_runner = FakeCommandRunner::default();
2784
2785        run(
2786            &create_context(&command_runner, &Default::default(), &Default::default()),
2787            create_simple_config(
2788                vec![
2789                    pool_build("foo", None),
2790                    pool_build("bar", None),
2791                    pool_build("baz", None),
2792                ],
2793                &["foo", "bar", "baz"],
2794            ),
2795            &[],
2796            DEFAULT_OPTIONS,
2797        )
2798        .await
2799        .unwrap();
2800
2801        assert_eq!(command_runner.max_concurrency(), 3);
2802    }
2803
2804    #[tokio::test]
2805    async fn limit_concurrency_of_builds_in_pool() {
2806        let command_runner = FakeCommandRunner::default();
2807
2808        run(
2809            &create_context(&command_runner, &Default::default(), &Default::default()),
2810            create_pool_config(
2811                vec![
2812                    pool_build("foo", limited_pool("qux")),
2813                    pool_build("bar", limited_pool("qux")),
2814                    pool_build("baz", limited_pool("qux")),
2815                ],
2816                &["foo", "bar", "baz"],
2817                &[("qux", 1)],
2818            ),
2819            &[],
2820            DEFAULT_OPTIONS,
2821        )
2822        .await
2823        .unwrap();
2824
2825        assert_eq!(command_runner.commands().len(), 3);
2826        assert_eq!(command_runner.max_concurrency(), 1);
2827    }
2828
2829    #[tokio::test]
2830    async fn limit_concurrency_of_builds_in_pool_with_depth_of_two() {
2831        let command_runner = FakeCommandRunner::default();
2832
2833        run(
2834            &create_context(&command_runner, &Default::default(), &Default::default()),
2835            create_pool_config(
2836                vec![
2837                    pool_build("foo", limited_pool("quux")),
2838                    pool_build("bar", limited_pool("quux")),
2839                    pool_build("baz", limited_pool("quux")),
2840                    pool_build("qux", limited_pool("quux")),
2841                ],
2842                &["foo", "bar", "baz", "qux"],
2843                &[("quux", 2)],
2844            ),
2845            &[],
2846            DEFAULT_OPTIONS,
2847        )
2848        .await
2849        .unwrap();
2850
2851        assert_eq!(command_runner.commands().len(), 4);
2852        assert_eq!(command_runner.max_concurrency(), 2);
2853    }
2854
2855    #[tokio::test]
2856    async fn run_builds_in_different_pools_concurrently() {
2857        let command_runner = FakeCommandRunner::default();
2858
2859        run(
2860            &create_context(&command_runner, &Default::default(), &Default::default()),
2861            create_pool_config(
2862                vec![
2863                    pool_build("foo", limited_pool("baz")),
2864                    pool_build("bar", limited_pool("qux")),
2865                ],
2866                &["foo", "bar"],
2867                &[("baz", 1), ("qux", 1)],
2868            ),
2869            &[],
2870            DEFAULT_OPTIONS,
2871        )
2872        .await
2873        .unwrap();
2874
2875        assert_eq!(command_runner.max_concurrency(), 2);
2876    }
2877
2878    #[tokio::test]
2879    async fn run_build_in_pool_of_huge_depth() {
2880        let command_runner = FakeCommandRunner::default();
2881
2882        run(
2883            &create_context(&command_runner, &Default::default(), &Default::default()),
2884            create_pool_config(
2885                vec![pool_build("foo", limited_pool("bar"))],
2886                &["foo"],
2887                &[("bar", usize::MAX)],
2888            ),
2889            &[],
2890            DEFAULT_OPTIONS,
2891        )
2892        .await
2893        .unwrap();
2894
2895        assert_eq!(command_runner.commands(), ["touch foo"]);
2896    }
2897
2898    #[tokio::test]
2899    async fn run_build_in_console_pool() {
2900        let command_runner = FakeCommandRunner::new(
2901            [(
2902                "touch foo".into(),
2903                Output {
2904                    status: ExitStatus::default(),
2905                    stdout: b"bar\n".into(),
2906                    stderr: b"baz\n".into(),
2907                },
2908            )]
2909            .into_iter()
2910            .collect(),
2911        );
2912        let console = FakeConsole::default();
2913
2914        run(
2915            &create_context(&command_runner, &console, &Default::default()),
2916            create_simple_config(
2917                vec![explicit_build(
2918                    vec!["foo".into()],
2919                    Rule::new("touch foo".into(), Some("build foo".into()))
2920                        .with_pool(Some(Pool::Console)),
2921                    vec![],
2922                )],
2923                &["foo"],
2924            ),
2925            &[],
2926            DEFAULT_OPTIONS,
2927        )
2928        .await
2929        .unwrap();
2930
2931        assert_eq!(command_runner.commands(), Vec::<String>::new());
2932        assert_eq!(command_runner.console_commands(), ["touch foo"]);
2933        assert_eq!(console.stdout(), "build foo\n");
2934        assert_eq!(console.stderr(), "");
2935    }
2936
2937    #[tokio::test]
2938    async fn limit_concurrency_of_builds_in_console_pool() {
2939        let command_runner = FakeCommandRunner::default();
2940
2941        run(
2942            &create_context(&command_runner, &Default::default(), &Default::default()),
2943            create_simple_config(
2944                vec![
2945                    pool_build("foo", Some(Pool::Console)),
2946                    pool_build("bar", Some(Pool::Console)),
2947                    pool_build("baz", Some(Pool::Console)),
2948                ],
2949                &["foo", "bar", "baz"],
2950            ),
2951            &[],
2952            DEFAULT_OPTIONS,
2953        )
2954        .await
2955        .unwrap();
2956
2957        assert_eq!(command_runner.console_commands().len(), 3);
2958        assert_eq!(command_runner.max_concurrency(), 1);
2959    }
2960
2961    #[tokio::test]
2962    async fn write_debug_log_of_failed_build_in_console_pool() {
2963        let command_runner =
2964            FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
2965        let console = FakeConsole::default();
2966
2967        assert_eq!(
2968            run(
2969                &create_context(&command_runner, &console, &Default::default()),
2970                create_simple_config(
2971                    vec![explicit_build(
2972                        vec!["foo".into()],
2973                        Rule::new("exit 1".into(), None).with_pool(Some(Pool::Console)),
2974                        vec![],
2975                    )],
2976                    &["foo"],
2977                ),
2978                &[],
2979                RunOptions {
2980                    debug: true,
2981                    dry_run: false,
2982                    profile: false,
2983                },
2984            )
2985            .await,
2986            Err(BuildError::Build)
2987        );
2988        assert_eq!(command_runner.console_commands(), ["exit 1"]);
2989        assert_eq!(
2990            console.stderr(),
2991            "turtle: command: exit 1\nturtle: exit status: 1\n"
2992        );
2993    }
2994
2995    #[tokio::test]
2996    async fn lock_console_during_command_in_console_pool() {
2997        let command_runner = FakeCommandRunner::default();
2998        let context = create_run_context(&command_runner, &Default::default(), &[]);
2999        let rule = Rule::new("foo".into(), None).with_pool(Some(Pool::Console));
3000        let mut future = pin!(run_rule(&context, &rule));
3001
3002        assert!(poll!(&mut future).is_pending());
3003        assert_eq!(command_runner.console_commands(), ["foo"]);
3004        assert!(context.build().console().try_lock().is_err());
3005
3006        future.await.unwrap();
3007
3008        assert!(context.build().console().try_lock().is_ok());
3009    }
3010
3011    #[tokio::test]
3012    async fn flush_console_before_command_in_console_pool() {
3013        let command_runner = FakeCommandRunner::default();
3014        let console = FakeConsole::default();
3015        let context = create_run_context(&command_runner, &console, &[]);
3016        let rule = Rule::new("foo".into(), Some("bar".into())).with_pool(Some(Pool::Console));
3017        let mut future = pin!(run_rule(&context, &rule));
3018
3019        assert!(poll!(&mut future).is_pending());
3020        assert_eq!(command_runner.console_commands(), ["foo"]);
3021        assert_eq!(console.flushed_stdout(), "bar\n");
3022
3023        future.await.unwrap();
3024    }
3025
3026    #[tokio::test]
3027    async fn wait_for_console_before_command_in_console_pool() {
3028        let command_runner = FakeCommandRunner::default();
3029        let context = create_run_context(&command_runner, &Default::default(), &[]);
3030        let rule = Rule::new("foo".into(), None).with_pool(Some(Pool::Console));
3031        let console = context.build().console().lock().await;
3032        let mut future = pin!(run_rule(&context, &rule));
3033
3034        for _ in 0..POLL_COUNT {
3035            assert!(poll!(&mut future).is_pending());
3036        }
3037
3038        assert_eq!(command_runner.console_commands(), Vec::<String>::new());
3039
3040        drop(console);
3041        future.await.unwrap();
3042
3043        assert_eq!(command_runner.console_commands(), ["foo"]);
3044    }
3045
3046    #[tokio::test]
3047    async fn run_command_concurrently_with_command_in_console_pool() {
3048        let command_runner = FakeCommandRunner::default();
3049        let context = create_run_context(&command_runner, &Default::default(), &[]);
3050        let foo = Rule::new("foo".into(), None).with_pool(Some(Pool::Console));
3051        let bar = Rule::new("bar".into(), None);
3052        let mut foo_future = pin!(run_rule(&context, &foo));
3053        let mut bar_future = pin!(run_rule(&context, &bar));
3054
3055        assert!(poll!(&mut foo_future).is_pending());
3056        assert!(poll!(&mut bar_future).is_pending());
3057        assert_eq!(command_runner.console_commands(), ["foo"]);
3058        assert_eq!(command_runner.commands(), ["bar"]);
3059
3060        foo_future.await.unwrap();
3061        bar_future.await.unwrap();
3062    }
3063
3064    #[tokio::test]
3065    async fn run_command_without_locking_console() {
3066        let command_runner = FakeCommandRunner::default();
3067        let console = FakeConsole::default();
3068        let context = create_run_context(&command_runner, &console, &[]);
3069        let rule = Rule::new("foo".into(), Some("bar".into()));
3070        let mut future = pin!(run_rule(&context, &rule));
3071
3072        assert!(poll!(&mut future).is_pending());
3073        assert_eq!(command_runner.commands(), ["foo"]);
3074        assert!(context.build().console().try_lock().is_ok());
3075        assert_eq!(console.stdout(), "");
3076
3077        future.await.unwrap();
3078
3079        assert_eq!(console.stdout(), "bar\n");
3080    }
3081
3082    #[tokio::test]
3083    async fn write_output_while_another_command_runs() {
3084        let command_runner = FakeCommandRunner::default();
3085        let console = FakeConsole::default();
3086        let context = create_run_context(&command_runner, &console, &[]);
3087        let foo = Rule::new("foo".into(), Some("foo".into()));
3088        let bar = Rule::new("bar".into(), Some("bar".into()));
3089        let mut foo_future = pin!(run_rule(&context, &foo));
3090
3091        assert!(poll!(&mut foo_future).is_pending());
3092
3093        run_rule(&context, &bar).await.unwrap();
3094
3095        assert_eq!(command_runner.commands(), ["foo", "bar"]);
3096        assert_eq!(console.stdout(), "bar\n");
3097
3098        foo_future.await.unwrap();
3099
3100        assert_eq!(console.stdout(), "bar\nfoo\n");
3101    }
3102
3103    #[tokio::test]
3104    async fn release_pool_before_waiting_for_console() {
3105        let command_runner = FakeCommandRunner::default();
3106        let context = create_run_context(&command_runner, &Default::default(), &[("baz", 1)]);
3107        let foo = Rule::new("foo".into(), None).with_pool(limited_pool("baz"));
3108        let bar = Rule::new("bar".into(), None).with_pool(limited_pool("baz"));
3109        let console = context.build().console().lock().await;
3110        let mut foo_future = pin!(run_rule(&context, &foo));
3111        let mut bar_future = pin!(run_rule(&context, &bar));
3112
3113        for _ in 0..POLL_COUNT {
3114            assert!(poll!(&mut foo_future).is_pending());
3115        }
3116
3117        for _ in 0..POLL_COUNT {
3118            assert!(poll!(&mut bar_future).is_pending());
3119        }
3120
3121        assert_eq!(command_runner.commands(), ["foo", "bar"]);
3122
3123        drop(console);
3124        foo_future.await.unwrap();
3125        bar_future.await.unwrap();
3126    }
3127
3128    #[tokio::test]
3129    async fn wait_for_pool_before_locking_console() {
3130        let command_runner = FakeCommandRunner::default();
3131        let console = FakeConsole::default();
3132        let context = create_run_context(&command_runner, &console, &[("bar", 1)]);
3133        let rule = Rule::new("foo".into(), Some("foo".into())).with_pool(limited_pool("bar"));
3134        let permit = context.pool(rule.pool()).await.unwrap();
3135        let mut future = pin!(run_rule(&context, &rule));
3136
3137        for _ in 0..POLL_COUNT {
3138            assert!(poll!(&mut future).is_pending());
3139        }
3140
3141        assert_eq!(command_runner.commands(), Vec::<String>::new());
3142        assert_eq!(console.stdout(), "");
3143        assert!(context.build().console().try_lock().is_ok());
3144
3145        drop(permit);
3146        future.await.unwrap();
3147
3148        assert_eq!(command_runner.commands(), ["foo"]);
3149        assert_eq!(console.stdout(), "foo\n");
3150    }
3151
3152    #[tokio::test]
3153    async fn keep_pool_until_command_finishes_on_console_error() {
3154        let command_runner = FakeCommandRunner::default();
3155        let context = create_run_context(&command_runner, &FakeConsole::failing(), &[("baz", 1)]);
3156        let foo = Rule::new("foo".into(), Some("foo".into())).with_pool(limited_pool("baz"));
3157        let bar = Rule::new("bar".into(), None).with_pool(limited_pool("baz"));
3158        let mut foo_future = pin!(run_rule(&context, &foo));
3159        let mut bar_future = pin!(run_rule(&context, &bar));
3160
3161        assert!(poll!(&mut foo_future).is_pending());
3162        assert!(poll!(&mut bar_future).is_pending());
3163        assert_eq!(command_runner.commands(), ["foo"]);
3164        assert!(foo_future.await.is_err());
3165    }
3166
3167    fn sort_file_inputs<'a>(
3168        (phony_inputs, file_inputs): (Vec<&'a Arc<str>>, Vec<&'a Arc<str>>),
3169    ) -> (Vec<&'a Arc<str>>, Vec<&'a Arc<str>>) {
3170        (phony_inputs, file_inputs.into_iter().sorted().collect_vec())
3171    }
3172
3173    #[test]
3174    fn classify_phony_and_file_inputs() {
3175        let build = explicit_build(
3176            vec!["foo".into()],
3177            Rule::new("".into(), None),
3178            vec!["bar".into(), "baz".into(), "qux".into(), "bar".into()],
3179        );
3180        let context = RunContext::new(
3181            create_context(
3182                &Default::default(),
3183                &Default::default(),
3184                &Default::default(),
3185            ),
3186            create_simple_config(
3187                vec![
3188                    build.clone(),
3189                    Build::new(vec!["bar".into()], vec![], None, vec![], vec![], None),
3190                    explicit_build(vec!["baz".into()], Rule::new("".into(), None), vec![]),
3191                ],
3192                &[],
3193            ),
3194            BuildGraph::new(&Default::default()),
3195            Default::default(),
3196            DEFAULT_OPTIONS,
3197        );
3198
3199        assert_eq!(
3200            sort_file_inputs(classify_inputs(
3201                &context,
3202                &build,
3203                &["quux".into(), "baz".into()],
3204                &[&"qux".into(), &"corge".into()],
3205            )),
3206            (
3207                vec![&"bar".into()],
3208                vec![
3209                    &"baz".into(),
3210                    &"corge".into(),
3211                    &"quux".into(),
3212                    &"qux".into()
3213                ]
3214            )
3215        );
3216    }
3217
3218    #[test]
3219    fn classify_duplicate_header_inputs() {
3220        let build = explicit_build(
3221            vec!["foo".into()],
3222            Rule::new("".into(), None),
3223            vec!["bar".into()],
3224        );
3225        let context = RunContext::new(
3226            create_context(
3227                &Default::default(),
3228                &Default::default(),
3229                &Default::default(),
3230            ),
3231            create_simple_config(vec![build.clone()], &[]),
3232            BuildGraph::new(&Default::default()),
3233            Default::default(),
3234            DEFAULT_OPTIONS,
3235        );
3236
3237        assert_eq!(
3238            sort_file_inputs(classify_inputs(
3239                &context,
3240                &build,
3241                &[],
3242                &[&"baz".into(), &"bar".into(), &"qux".into(), &"baz".into()],
3243            )),
3244            (vec![], vec![&"bar".into(), &"baz".into(), &"qux".into()])
3245        );
3246    }
3247
3248    #[test]
3249    fn classify_inputs_in_deterministic_order() {
3250        let build = explicit_build(vec!["foo".into()], Rule::new("".into(), None), vec![]);
3251        let context = create_build_run_context(
3252            &Default::default(),
3253            &Default::default(),
3254            vec![build.clone()],
3255        );
3256        let inputs = (0..64)
3257            .map(|index| index.to_string().into())
3258            .collect::<Vec<Arc<str>>>();
3259        let inputs = inputs.iter().collect_vec();
3260
3261        assert_eq!(
3262            classify_inputs(&context, &build, &[], &inputs),
3263            classify_inputs(&context, &build, &[], &inputs)
3264        );
3265    }
3266
3267    mod dry_run {
3268        use super::*;
3269        use pretty_assertions::assert_eq;
3270        use std::path::PathBuf;
3271
3272        const OPTIONS: RunOptions = RunOptions {
3273            dry_run: true,
3274            ..DEFAULT_OPTIONS
3275        };
3276
3277        fn create_database_context(
3278            console: &FakeConsole,
3279            database: &FakeDatabase,
3280            file_system: &FakeFileSystem,
3281        ) -> Arc<Context> {
3282            Context::new(
3283                FakeCommandRunner::default(),
3284                Mutex::new(console.clone()).into(),
3285                database.clone(),
3286                file_system.clone(),
3287                Default::default(),
3288            )
3289            .into()
3290        }
3291
3292        fn described_rule(output: &str) -> Rule {
3293            Rule::new(format!("touch {output}"), Some(format!("build {output}")))
3294        }
3295
3296        fn described_build(output: &str, inputs: &[&str]) -> Build {
3297            explicit_build(
3298                vec![output.into()],
3299                described_rule(output),
3300                inputs.iter().map(|&input| input.into()).collect(),
3301            )
3302        }
3303
3304        fn phony_build(output: &str, inputs: &[&str]) -> Build {
3305            Build::new(
3306                vec![output.into()],
3307                vec![],
3308                None,
3309                inputs.iter().map(|&input| input.into()).collect(),
3310                vec![],
3311                None,
3312            )
3313        }
3314
3315        #[tokio::test]
3316        async fn run_no_command() {
3317            let command_runner = FakeCommandRunner::default();
3318
3319            run(
3320                &create_context(&command_runner, &Default::default(), &Default::default()),
3321                create_simple_config(vec![described_build("foo", &[])], &["foo"]),
3322                &[],
3323                OPTIONS,
3324            )
3325            .await
3326            .unwrap();
3327
3328            assert_eq!(command_runner.commands(), Vec::<String>::new());
3329        }
3330
3331        #[tokio::test]
3332        async fn write_description() {
3333            let console = FakeConsole::default();
3334
3335            run(
3336                &create_context(&Default::default(), &console, &Default::default()),
3337                create_simple_config(vec![described_build("foo", &[])], &["foo"]),
3338                &[],
3339                OPTIONS,
3340            )
3341            .await
3342            .unwrap();
3343
3344            assert_eq!(console.stdout(), "build foo\n");
3345            assert_eq!(console.stderr(), "");
3346        }
3347
3348        #[tokio::test]
3349        async fn write_debug_log() {
3350            let console = FakeConsole::default();
3351
3352            run(
3353                &create_context(&Default::default(), &console, &Default::default()),
3354                create_simple_config(vec![described_build("foo", &[])], &["foo"]),
3355                &[],
3356                RunOptions {
3357                    debug: true,
3358                    ..OPTIONS
3359                },
3360            )
3361            .await
3362            .unwrap();
3363
3364            assert_eq!(console.stdout(), "build foo\n");
3365            assert_eq!(console.stderr(), "turtle: command: touch foo\n");
3366        }
3367
3368        #[tokio::test]
3369        async fn prepare_no_output_directory() {
3370            let context = create_context(
3371                &Default::default(),
3372                &Default::default(),
3373                &Default::default(),
3374            );
3375
3376            run(
3377                &context,
3378                create_simple_config(vec![described_build("foo/bar", &[])], &["foo/bar"]),
3379                &[],
3380                OPTIONS,
3381            )
3382            .await
3383            .unwrap();
3384
3385            assert!(!context.file_system().exists("foo".as_ref()).await.unwrap());
3386        }
3387
3388        #[tokio::test]
3389        async fn record_no_output() {
3390            let file_system = FakeFileSystem::default();
3391            let context = create_context(&Default::default(), &Default::default(), &file_system);
3392
3393            file_system.write_file("foo.c", "");
3394
3395            run(
3396                &context,
3397                Config::new(
3398                    create_outputs(vec![described_build("foo.o", &["foo.c"])]),
3399                    ["foo.o".into()].into_iter().collect(),
3400                    [("foo.o".into(), "foo.c".into())].into_iter().collect(),
3401                    Default::default(),
3402                    None,
3403                )
3404                .into(),
3405                &[],
3406                OPTIONS,
3407            )
3408            .await
3409            .unwrap();
3410
3411            assert_eq!(
3412                context.database().get_outputs().await.unwrap(),
3413                Vec::<String>::new()
3414            );
3415            assert_eq!(context.database().get_source("foo.o").await.unwrap(), None);
3416        }
3417
3418        #[tokio::test]
3419        async fn run_command_after_dry_run() {
3420            let command_runner = FakeCommandRunner::default();
3421            let file_system = FakeFileSystem::default();
3422            let context = create_context(&command_runner, &Default::default(), &file_system);
3423            let config = create_simple_config(
3424                vec![
3425                    described_build("foo", &["bar"]),
3426                    phony_build("bar", &["baz"]),
3427                ],
3428                &["foo"],
3429            );
3430
3431            file_system.write_file("foo", "");
3432            file_system.write_file("baz", "");
3433
3434            run(&context, config.clone(), &[], OPTIONS).await.unwrap();
3435            run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
3436
3437            assert_eq!(command_runner.commands(), ["touch foo"]);
3438        }
3439
3440        #[tokio::test]
3441        async fn write_no_description_of_up_to_date_build() {
3442            let console = FakeConsole::default();
3443            let database = FakeDatabase::default();
3444            let file_system = FakeFileSystem::default();
3445            let config = create_simple_config(vec![described_build("foo", &["bar"])], &["foo"]);
3446
3447            file_system.write_file("foo", "");
3448            file_system.write_file("bar", "");
3449
3450            run(
3451                &create_database_context(&Default::default(), &database, &file_system),
3452                config.clone(),
3453                &[],
3454                DEFAULT_OPTIONS,
3455            )
3456            .await
3457            .unwrap();
3458            run(
3459                &create_database_context(&console, &database, &file_system),
3460                config,
3461                &[],
3462                OPTIONS,
3463            )
3464            .await
3465            .unwrap();
3466
3467            assert_eq!(console.stdout(), "");
3468        }
3469
3470        #[tokio::test]
3471        async fn write_description_on_content_update_of_input() {
3472            let console = FakeConsole::default();
3473            let database = FakeDatabase::default();
3474            let file_system = FakeFileSystem::default();
3475            let config = create_simple_config(vec![described_build("foo", &["bar"])], &["foo"]);
3476
3477            file_system.write_file("foo", "");
3478            file_system.write_file("bar", "");
3479
3480            run(
3481                &create_database_context(&Default::default(), &database, &file_system),
3482                config.clone(),
3483                &[],
3484                DEFAULT_OPTIONS,
3485            )
3486            .await
3487            .unwrap();
3488
3489            file_system.write_file("bar", "bar");
3490
3491            run(
3492                &create_database_context(&console, &database, &file_system),
3493                config,
3494                &[],
3495                OPTIONS,
3496            )
3497            .await
3498            .unwrap();
3499
3500            assert_eq!(console.stdout(), "build foo\n");
3501        }
3502
3503        #[tokio::test]
3504        async fn write_description_of_missing_output() {
3505            let console = FakeConsole::default();
3506            let database = FakeDatabase::default();
3507            let file_system = FakeFileSystem::default();
3508            let config = create_simple_config(vec![described_build("foo", &["bar"])], &["foo"]);
3509
3510            file_system.write_file("bar", "");
3511
3512            run(
3513                &create_database_context(&Default::default(), &database, &file_system),
3514                config.clone(),
3515                &[],
3516                DEFAULT_OPTIONS,
3517            )
3518            .await
3519            .unwrap();
3520            run(
3521                &create_database_context(&console, &database, &file_system),
3522                config,
3523                &[],
3524                OPTIONS,
3525            )
3526            .await
3527            .unwrap();
3528
3529            assert_eq!(console.stdout(), "build foo\n");
3530        }
3531
3532        #[tokio::test]
3533        async fn write_description_of_dependent_of_missing_output() {
3534            let console = FakeConsole::default();
3535            let file_system = FakeFileSystem::default();
3536
3537            file_system.write_file("baz", "");
3538
3539            run(
3540                &create_context(&Default::default(), &console, &file_system),
3541                create_simple_config(
3542                    vec![
3543                        described_build("foo", &["bar"]),
3544                        described_build("bar", &["baz"]),
3545                    ],
3546                    &["foo"],
3547                ),
3548                &[],
3549                OPTIONS,
3550            )
3551            .await
3552            .unwrap();
3553
3554            assert_eq!(console.stdout(), "build bar\nbuild foo\n");
3555        }
3556
3557        #[tokio::test]
3558        async fn write_description_of_dependent_of_stale_build() {
3559            let console = FakeConsole::default();
3560            let database = FakeDatabase::default();
3561            let file_system = FakeFileSystem::default();
3562            let config = create_simple_config(
3563                vec![
3564                    described_build("foo", &["bar"]),
3565                    described_build("bar", &["baz"]),
3566                ],
3567                &["foo"],
3568            );
3569
3570            file_system.write_file("foo", "");
3571            file_system.write_file("bar", "");
3572            file_system.write_file("baz", "");
3573
3574            run(
3575                &create_database_context(&Default::default(), &database, &file_system),
3576                config.clone(),
3577                &[],
3578                DEFAULT_OPTIONS,
3579            )
3580            .await
3581            .unwrap();
3582
3583            file_system.write_file("baz", "baz");
3584
3585            run(
3586                &create_database_context(&console, &database, &file_system),
3587                config,
3588                &[],
3589                OPTIONS,
3590            )
3591            .await
3592            .unwrap();
3593
3594            assert_eq!(console.stdout(), "build bar\nbuild foo\n");
3595        }
3596
3597        #[tokio::test]
3598        async fn write_no_description_of_dependent_of_stale_order_only_input() {
3599            let console = FakeConsole::default();
3600            let database = FakeDatabase::default();
3601            let file_system = FakeFileSystem::default();
3602            let config = create_simple_config(
3603                vec![
3604                    Build::new(
3605                        vec!["foo".into()],
3606                        vec![],
3607                        described_rule("foo").into(),
3608                        vec![],
3609                        vec!["bar".into()],
3610                        None,
3611                    ),
3612                    described_build("bar", &["baz"]),
3613                ],
3614                &["foo"],
3615            );
3616
3617            file_system.write_file("foo", "");
3618            file_system.write_file("bar", "");
3619            file_system.write_file("baz", "");
3620
3621            run(
3622                &create_database_context(&Default::default(), &database, &file_system),
3623                config.clone(),
3624                &[],
3625                DEFAULT_OPTIONS,
3626            )
3627            .await
3628            .unwrap();
3629
3630            file_system.write_file("baz", "baz");
3631
3632            run(
3633                &create_database_context(&console, &database, &file_system),
3634                config,
3635                &[],
3636                OPTIONS,
3637            )
3638            .await
3639            .unwrap();
3640
3641            assert_eq!(console.stdout(), "build bar\n");
3642        }
3643
3644        #[tokio::test]
3645        async fn write_no_description_of_dependent_of_up_to_date_phony_input() {
3646            let console = FakeConsole::default();
3647            let database = FakeDatabase::default();
3648            let file_system = FakeFileSystem::default();
3649            let config = create_simple_config(
3650                vec![
3651                    described_build("foo", &["bar"]),
3652                    phony_build("bar", &["baz"]),
3653                ],
3654                &["foo"],
3655            );
3656
3657            file_system.write_file("foo", "");
3658            file_system.write_file("baz", "");
3659
3660            run(
3661                &create_database_context(&Default::default(), &database, &file_system),
3662                config.clone(),
3663                &[],
3664                DEFAULT_OPTIONS,
3665            )
3666            .await
3667            .unwrap();
3668            run(
3669                &create_database_context(&console, &database, &file_system),
3670                config,
3671                &[],
3672                OPTIONS,
3673            )
3674            .await
3675            .unwrap();
3676
3677            assert_eq!(console.stdout(), "");
3678        }
3679
3680        #[tokio::test]
3681        async fn write_description_on_update_of_phony_input() {
3682            let console = FakeConsole::default();
3683            let database = FakeDatabase::default();
3684            let file_system = FakeFileSystem::default();
3685            let config = create_simple_config(
3686                vec![
3687                    described_build("foo", &["bar"]),
3688                    phony_build("bar", &["baz"]),
3689                ],
3690                &["foo"],
3691            );
3692
3693            file_system.write_file("foo", "");
3694            file_system.write_file("baz", "");
3695
3696            run(
3697                &create_database_context(&Default::default(), &database, &file_system),
3698                config.clone(),
3699                &[],
3700                DEFAULT_OPTIONS,
3701            )
3702            .await
3703            .unwrap();
3704
3705            file_system.write_file("baz", "baz");
3706
3707            run(
3708                &create_database_context(&console, &database, &file_system),
3709                config,
3710                &[],
3711                OPTIONS,
3712            )
3713            .await
3714            .unwrap();
3715
3716            assert_eq!(console.stdout(), "build foo\n");
3717        }
3718
3719        #[tokio::test]
3720        async fn write_description_of_dependent_of_phony_input_without_input() {
3721            let console = FakeConsole::default();
3722            let database = FakeDatabase::default();
3723            let file_system = FakeFileSystem::default();
3724            let config = create_simple_config(
3725                vec![described_build("foo", &["bar"]), phony_build("bar", &[])],
3726                &["foo"],
3727            );
3728
3729            file_system.write_file("foo", "");
3730
3731            run(
3732                &create_database_context(&Default::default(), &database, &file_system),
3733                config.clone(),
3734                &[],
3735                DEFAULT_OPTIONS,
3736            )
3737            .await
3738            .unwrap();
3739            run(
3740                &create_database_context(&console, &database, &file_system),
3741                config,
3742                &[],
3743                OPTIONS,
3744            )
3745            .await
3746            .unwrap();
3747
3748            assert_eq!(console.stdout(), "build foo\n");
3749        }
3750
3751        #[tokio::test]
3752        async fn write_description_of_dependent_of_phony_input_with_stale_input() {
3753            let console = FakeConsole::default();
3754            let database = FakeDatabase::default();
3755            let file_system = FakeFileSystem::default();
3756            let config = create_simple_config(
3757                vec![
3758                    described_build("foo", &["bar"]),
3759                    phony_build("bar", &["baz"]),
3760                    described_build("baz", &["qux"]),
3761                ],
3762                &["foo"],
3763            );
3764
3765            file_system.write_file("foo", "");
3766            file_system.write_file("baz", "");
3767            file_system.write_file("qux", "");
3768
3769            run(
3770                &create_database_context(&Default::default(), &database, &file_system),
3771                config.clone(),
3772                &[],
3773                DEFAULT_OPTIONS,
3774            )
3775            .await
3776            .unwrap();
3777
3778            file_system.write_file("qux", "qux");
3779
3780            run(
3781                &create_database_context(&console, &database, &file_system),
3782                config,
3783                &[],
3784                OPTIONS,
3785            )
3786            .await
3787            .unwrap();
3788
3789            assert_eq!(console.stdout(), "build baz\nbuild foo\n");
3790        }
3791
3792        #[tokio::test]
3793        async fn write_description_of_dependent_of_stale_header_dependency() {
3794            let console = FakeConsole::default();
3795            let database = FakeDatabase::default();
3796            let file_system = FakeFileSystem::default();
3797            let config = create_simple_config(
3798                vec![
3799                    explicit_build(
3800                        vec!["foo.o".into()],
3801                        described_rule("foo.o").with_header_dependency(Some(
3802                            HeaderDependency::Make {
3803                                path: "foo.d".into(),
3804                            },
3805                        )),
3806                        vec!["foo.c".into()],
3807                    ),
3808                    described_build("foo.h", &["foo.h.in"]),
3809                ],
3810                &["foo.o", "foo.h"],
3811            );
3812
3813            file_system.write_file("foo.o", "");
3814            file_system.write_file("foo.c", "");
3815            file_system.write_file("foo.d", "foo.o: foo.h\n");
3816            file_system.write_file("foo.h", "");
3817            file_system.write_file("foo.h.in", "");
3818
3819            run(
3820                &create_database_context(&Default::default(), &database, &file_system),
3821                config.clone(),
3822                &[],
3823                DEFAULT_OPTIONS,
3824            )
3825            .await
3826            .unwrap();
3827
3828            file_system.write_file("foo.h.in", "foo");
3829
3830            run(
3831                &create_database_context(&console, &database, &file_system),
3832                config,
3833                &[],
3834                OPTIONS,
3835            )
3836            .await
3837            .unwrap();
3838
3839            assert_eq!(console.stdout(), "build foo.h\nbuild foo.o\n");
3840        }
3841
3842        #[tokio::test]
3843        async fn write_description_of_dependent_of_stale_dynamic_input() {
3844            let console = FakeConsole::default();
3845            let database = FakeDatabase::default();
3846            let file_system = FakeFileSystem::default();
3847            let config = create_simple_config(
3848                vec![
3849                    Build::new(
3850                        vec!["foo".into()],
3851                        vec![],
3852                        described_rule("foo").into(),
3853                        vec![],
3854                        vec!["foo.dd".into()],
3855                        Some("foo.dd".into()),
3856                    ),
3857                    described_build("bar", &["baz"]),
3858                ],
3859                &["foo"],
3860            );
3861
3862            file_system.write_file(
3863                "foo.dd",
3864                "ninja_dyndep_version = 1\nbuild foo: dyndep | bar\n",
3865            );
3866            file_system.write_file("foo", "");
3867            file_system.write_file("bar", "");
3868            file_system.write_file("baz", "");
3869
3870            run(
3871                &create_database_context(&Default::default(), &database, &file_system),
3872                config.clone(),
3873                &[],
3874                DEFAULT_OPTIONS,
3875            )
3876            .await
3877            .unwrap();
3878
3879            file_system.write_file("baz", "baz");
3880
3881            run(
3882                &create_database_context(&console, &database, &file_system),
3883                config,
3884                &[],
3885                OPTIONS,
3886            )
3887            .await
3888            .unwrap();
3889
3890            assert_eq!(console.stdout(), "build bar\nbuild foo\n");
3891        }
3892
3893        #[tokio::test]
3894        async fn write_description_of_dependent_of_stale_dynamic_module() {
3895            let console = FakeConsole::default();
3896            let file_system = FakeFileSystem::default();
3897
3898            run(
3899                &create_context(&Default::default(), &console, &file_system),
3900                create_simple_config(
3901                    vec![
3902                        Build::new(
3903                            vec!["foo".into()],
3904                            vec![],
3905                            described_rule("foo").into(),
3906                            vec![],
3907                            vec!["foo.dd".into()],
3908                            Some("foo.dd".into()),
3909                        ),
3910                        described_build("foo.dd", &[]),
3911                    ],
3912                    &["foo"],
3913                ),
3914                &[],
3915                OPTIONS,
3916            )
3917            .await
3918            .unwrap();
3919
3920            assert_eq!(console.stdout(), "build foo.dd\nbuild foo\n");
3921            assert_eq!(file_system.read_requests(), Vec::<PathBuf>::new());
3922        }
3923
3924        #[tokio::test]
3925        async fn fail_with_missing_input() {
3926            assert_eq!(
3927                run(
3928                    &create_context(
3929                        &Default::default(),
3930                        &Default::default(),
3931                        &Default::default()
3932                    ),
3933                    create_simple_config(vec![described_build("foo", &["bar"])], &["foo"]),
3934                    &[],
3935                    OPTIONS,
3936                )
3937                .await,
3938                Err(BuildError::FileNotFound("bar".into()))
3939            );
3940        }
3941    }
3942}