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