Skip to main content

turtle_build/
run.rs

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