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