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