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