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