Skip to main content

turtle_build/
run.rs

1mod context;
2mod file_cache;
3mod hash;
4mod header_dependency;
5mod log;
6mod options;
7
8use self::{
9    context::RunContext,
10    hash::{calculate_content_hash, calculate_timestamp_hash},
11    header_dependency::{exclude_show_includes, read_header_dependencies},
12    log::{debug, profile},
13};
14use crate::{
15    build_graph::{BuildGraph, BuildGraphError},
16    compile::compile_dynamic,
17    context::Context,
18    error::BuildError,
19    hash_type::HashType,
20    infrastructure::{Console, Metadata},
21    ir::{Build, Config, Pool, Rule},
22    parse::parse_dynamic,
23};
24use alloc::sync::Arc;
25use async_recursion::async_recursion;
26use futures::future::{FutureExt, try_join_all};
27use itertools::Itertools;
28pub use options::RunOptions;
29use std::{collections::HashMap, path::Path, process::Output};
30use tokio::{join, spawn, sync::MutexGuard, time::Instant, try_join};
31
32/// Runs builds.
33pub async fn run(
34    context: &Arc<Context>,
35    config: Arc<Config>,
36    outputs: &[String],
37    options: RunOptions,
38) -> Result<(), BuildError> {
39    let mut graph = BuildGraph::new(config.outputs());
40    let mut header_dependencies = HashMap::new();
41
42    for build in config
43        .outputs()
44        .values()
45        .filter(|build| build.rule().is_some())
46        .unique_by(|build| build.id())
47    {
48        let dependencies = context.database().get_header_dependencies(build.id())?;
49
50        graph.add_header_dependencies(&build.outputs()[0], &dependencies);
51        header_dependencies.insert(build.id(), dependencies);
52    }
53
54    let context = Arc::new(RunContext::new(
55        context.clone(),
56        config,
57        graph,
58        header_dependencies,
59        options,
60    ));
61
62    context
63        .build_graph()
64        .lock()
65        .await
66        .validate()
67        .map_err(|error| map_build_graph_error(&context, &error))?;
68
69    let result = try_join_all(
70        if outputs.is_empty() {
71            context
72                .config()
73                .default_outputs()
74                .iter()
75                .map(|output| {
76                    context
77                        .config()
78                        .outputs()
79                        .get(output.as_ref())
80                        .ok_or_else(|| BuildError::DefaultOutputNotFound(output.clone()))
81                })
82                .collect::<Result<Vec<_>, _>>()?
83        } else {
84            outputs
85                .iter()
86                .map(|output| {
87                    context
88                        .config()
89                        .outputs()
90                        .get(output.as_str())
91                        .ok_or_else(|| BuildError::OutputNotFound(output.clone()))
92                })
93                .collect::<Result<Vec<_>, _>>()?
94        }
95        .into_iter()
96        .map(|build| run_build(context.clone(), build)),
97    )
98    .await;
99
100    context.build().database().flush().await?;
101
102    result.map(|_| ())
103}
104
105#[async_recursion]
106async fn run_build(context: Arc<RunContext>, build: &Arc<Build>) -> Result<(), BuildError> {
107    // Do not inline this to avoid holding a lock of build futures across an await point.
108    let future = context
109        .build_futures()
110        .entry_async(build.id())
111        .await
112        .or_insert_with(|| spawn_build(context.clone(), build.clone()).boxed().shared())
113        .get()
114        .clone();
115
116    future.await
117}
118
119async fn spawn_build(context: Arc<RunContext>, build: Arc<Build>) -> Result<(), BuildError> {
120    spawn(async move {
121        let (_, output_metadata) = try_join!(
122            try_join_all(
123                build
124                    .inputs()
125                    .iter()
126                    .chain(build.order_only_inputs())
127                    .map(|input| build_input(context.clone(), input)),
128            ),
129            get_output_metadata(&context, &build),
130        )?;
131
132        // TODO Consider caching dynamic modules.
133        let dynamic_config = if let Some(dynamic_module) = build.dynamic_module() {
134            let config = compile_dynamic(&parse_dynamic(
135                &context
136                    .build()
137                    .file_system()
138                    .read_file_to_string(dynamic_module.as_ref().as_ref())
139                    .await?,
140            )?)?;
141
142            context
143                .build_graph()
144                .lock()
145                .await
146                .validate_dynamic(&config)
147                .map_err(|error| map_build_graph_error(&context, &error))?;
148
149            Some(config)
150        } else {
151            None
152        };
153
154        let dynamic_inputs = if let Some(config) = &dynamic_config {
155            build
156                .outputs()
157                .iter()
158                .chain(build.implicit_outputs())
159                .find_map(|output| config.outputs().get(output.as_ref()))
160                .map(|build| build.inputs())
161                .ok_or_else(|| BuildError::DynamicDependencyNotFound(build.clone()))?
162        } else {
163            &[]
164        };
165
166        try_join_all(
167            dynamic_inputs
168                .iter()
169                .map(|input| build_input(context.clone(), input)),
170        )
171        .await?;
172
173        if build.rule().is_none() {
174            // TODO Consider dropping this case by assuming that outputs of phony
175            // rules are always virtual.
176            invalidate_outputs(&context, &build).await;
177        }
178
179        let dependencies = context.header_dependencies(build.id());
180
181        try_join_all(
182            dependencies
183                .iter()
184                .filter_map(|dependency| context.config().outputs().get(dependency.as_str()))
185                .map(|build| run_build(context.clone(), build)),
186        )
187        .await?;
188
189        let header_dependencies =
190            filter_existing_header_dependencies(&context, dependencies).await?;
191
192        let (phony_inputs, file_inputs) =
193            classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
194        let mut timestamp_hash =
195            calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
196
197        if let Some(metadata) = &output_metadata
198            && Some(timestamp_hash)
199                == context
200                    .build()
201                    .database()
202                    .get_hash(HashType::Timestamp, build.id())?
203        {
204            cache_output_metadata(&context, &build, metadata).await;
205
206            return Ok(());
207        }
208
209        let mut content_hash =
210            calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
211
212        if output_metadata.is_some()
213            && Some(content_hash)
214                == context
215                    .build()
216                    .database()
217                    .get_hash(HashType::Content, build.id())?
218        {
219            return Ok(());
220        } else if let Some(rule) = build.rule() {
221            try_join_all(
222                build
223                    .outputs()
224                    .iter()
225                    .chain(build.implicit_outputs())
226                    .map(|path| prepare_directory(&context, path.as_ref())),
227            )
228            .await?;
229
230            let output = run_rule(&context, rule).await;
231
232            invalidate_outputs(&context, &build).await;
233
234            let new_header_dependencies =
235                read_header_dependencies(&context, rule, &output?).await?;
236
237            context
238                .build()
239                .database()
240                .set_header_dependencies(build.id(), &new_header_dependencies)?;
241
242            for output in build.outputs() {
243                context.build().database().set_output(output)?;
244
245                if let Some(source) = context.config().source_map().get(output) {
246                    context.build().database().set_source(output, source)?;
247                }
248            }
249
250            if header_dependencies != new_header_dependencies {
251                let header_dependencies =
252                    filter_existing_header_dependencies(&context, &new_header_dependencies).await?;
253                let (phony_inputs, file_inputs) =
254                    classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
255
256                timestamp_hash =
257                    calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
258                content_hash =
259                    calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
260            }
261        }
262
263        context
264            .build()
265            .database()
266            .set_hash(HashType::Timestamp, build.id(), timestamp_hash)?;
267        context
268            .build()
269            .database()
270            .set_hash(HashType::Content, build.id(), content_hash)?;
271
272        Ok(())
273    })
274    .await?
275}
276
277async fn build_input(context: Arc<RunContext>, input: &str) -> Result<(), BuildError> {
278    if let Some(build) = context.config().outputs().get(input) {
279        run_build(context.clone(), build).await
280    } else {
281        check_file_existence(&context, input).await
282    }
283}
284
285async fn get_output_metadata(
286    context: &RunContext,
287    build: &Build,
288) -> Result<Option<Vec<Metadata>>, BuildError> {
289    Ok(try_join_all(
290        build
291            .outputs()
292            .iter()
293            .chain(build.implicit_outputs())
294            .map(|path| {
295                context
296                    .build()
297                    .file_system()
298                    .metadata(path.as_ref().as_ref())
299            }),
300    )
301    .await?
302    .into_iter()
303    .collect())
304}
305
306async fn cache_output_metadata(context: &RunContext, build: &Build, metadata: &[Metadata]) {
307    // Inputs of phony builds might write their outputs.
308    if build.rule().is_none() {
309        return;
310    }
311
312    for (path, &metadata) in build
313        .outputs()
314        .iter()
315        .chain(build.implicit_outputs())
316        .zip(metadata)
317    {
318        context
319            .file_cache()
320            .set_metadata(path.as_ref().as_ref(), metadata)
321            .await;
322    }
323}
324
325async fn filter_existing_header_dependencies<'a>(
326    context: &RunContext,
327    dependencies: &'a [String],
328) -> Result<Vec<&'a str>, BuildError> {
329    let mut existing_dependencies = vec![];
330
331    for dependency in dependencies {
332        if context.file_cache().exists(dependency.as_ref()).await? {
333            existing_dependencies.push(dependency.as_str());
334        }
335    }
336
337    Ok(existing_dependencies)
338}
339
340async fn check_file_existence(context: &RunContext, path: &str) -> Result<(), BuildError> {
341    if !context.file_cache().exists(path.as_ref()).await? {
342        return Err(BuildError::FileNotFound(
343            context
344                .build()
345                .database()
346                .get_source(path)?
347                .unwrap_or_else(|| path.into()),
348        ));
349    }
350
351    Ok(())
352}
353
354async fn prepare_directory(context: &RunContext, path: impl AsRef<Path>) -> Result<(), BuildError> {
355    if let Some(directory) = path.as_ref().parent() {
356        context
357            .build()
358            .file_system()
359            .create_directory(directory)
360            .await?;
361    }
362
363    Ok(())
364}
365
366async fn invalidate_outputs(context: &RunContext, build: &Build) {
367    for output in build.outputs().iter().chain(build.implicit_outputs()) {
368        context
369            .file_cache()
370            .invalidate(output.as_ref().as_ref())
371            .await;
372    }
373}
374
375fn classify_inputs<'a>(
376    context: &'a RunContext,
377    build: &'a Build,
378    dynamic_inputs: &'a [Arc<str>],
379    header_dependencies: &'a [&'a str],
380) -> (Vec<&'a str>, Vec<&'a str>) {
381    let (phony_inputs, file_inputs) = build
382        .inputs()
383        .iter()
384        .chain(dynamic_inputs)
385        .map(AsRef::as_ref)
386        .unique()
387        .partition::<Vec<_>, _>(|&input| {
388            context
389                .config()
390                .outputs()
391                .get(input)
392                .map_or_default(|build| build.rule().is_none())
393        });
394
395    (
396        phony_inputs,
397        file_inputs
398            .into_iter()
399            .chain(header_dependencies.iter().copied())
400            .unique()
401            .collect(),
402    )
403}
404
405async fn run_rule(context: &RunContext, rule: &Rule) -> Result<Output, BuildError> {
406    let ((output, duration), mut console) = if rule.pool() == Some(&Pool::Console) {
407        let mut console = write_description(context, rule).await?;
408
409        console.flush().await?;
410
411        let time = Instant::now();
412        let status = context
413            .build()
414            .command_runner()
415            .run_with_console(rule.command())
416            .await?;
417
418        (
419            (
420                Output {
421                    status,
422                    stdout: vec![],
423                    stderr: vec![],
424                },
425                Instant::now() - time,
426            ),
427            console,
428        )
429    } else {
430        let permit = context.pool(rule.pool()).await?;
431        let (output, console) = join!(
432            async {
433                let time = Instant::now();
434                let output = context.build().command_runner().run(rule.command()).await?;
435
436                drop(permit);
437
438                Ok::<_, BuildError>((output, Instant::now() - time))
439            },
440            write_description(context, rule)
441        );
442
443        (output?, console?)
444    };
445
446    profile!(context, console, "duration: {} ms", duration.as_millis());
447
448    console
449        .write_stdout(&exclude_show_includes(rule, &output.stdout))
450        .await?;
451    console.write_stderr(&output.stderr).await?;
452
453    if !output.status.success() {
454        debug!(
455            context,
456            console,
457            "exit status: {}",
458            output
459                .status
460                .code()
461                .map(|code| code.to_string())
462                .unwrap_or_else(|| "-".into())
463        );
464
465        return Err(BuildError::Build);
466    }
467
468    Ok(output)
469}
470
471async fn write_description<'a>(
472    context: &'a RunContext,
473    rule: &Rule,
474) -> Result<MutexGuard<'a, dyn Console + Send + Sync>, BuildError> {
475    let mut console = context.build().console().lock().await;
476
477    if let Some(description) = rule.description() {
478        console.write_stderr(description.as_bytes()).await?;
479        console.write_stderr(b"\n").await?;
480    }
481
482    debug!(context, console, "command: {}", rule.command());
483
484    Ok(console)
485}
486
487fn map_build_graph_error(context: &RunContext, error: &BuildGraphError) -> BuildError {
488    match error {
489        BuildGraphError::CircularDependency(outputs) => {
490            match outputs
491                .iter()
492                .map(|output| {
493                    Ok(context
494                        .build()
495                        .database()
496                        .get_source(output)?
497                        .map(|string| string.into())
498                        .unwrap_or_else(|| output.clone()))
499                })
500                .collect::<Result<Vec<_>, BuildError>>()
501            {
502                Ok(outputs) => {
503                    BuildGraphError::CircularDependency(outputs.into_iter().dedup().collect())
504                        .into()
505                }
506                Err(error) => error,
507            }
508        }
509        BuildGraphError::OutputNotFound(_) => error.clone().into(),
510    }
511}
512
513#[cfg(test)]
514mod tests {
515    use super::*;
516    use crate::{
517        infrastructure::{FakeCommandRunner, FakeConsole, FakeDatabase, FakeFileSystem, FileError},
518        ir::HeaderDependency,
519    };
520    use core::{num::NonZeroUsize, pin::pin};
521    use futures::poll;
522    use pretty_assertions::{assert_eq, assert_ne};
523    use regex::Regex;
524    #[cfg(unix)]
525    use std::os::unix::process::ExitStatusExt;
526    #[cfg(windows)]
527    use std::os::windows::process::ExitStatusExt;
528    use std::{collections::HashMap, process::ExitStatus};
529    use tokio::{sync::Mutex, task::yield_now};
530
531    const DEFAULT_OPTIONS: RunOptions = RunOptions {
532        debug: false,
533        profile: false,
534    };
535    const POLL_COUNT: usize = 8;
536
537    fn create_context(
538        command_runner: &FakeCommandRunner,
539        console: &FakeConsole,
540        file_system: &FakeFileSystem,
541    ) -> Arc<Context> {
542        Context::new(
543            command_runner.clone(),
544            Mutex::new(console.clone()).into(),
545            FakeDatabase::default(),
546            file_system.clone(),
547        )
548        .into()
549    }
550
551    fn create_outputs(builds: Vec<Build>) -> HashMap<Arc<str>, Arc<Build>> {
552        builds
553            .into_iter()
554            .map(Arc::new)
555            .flat_map(|build| {
556                build
557                    .outputs()
558                    .iter()
559                    .chain(build.implicit_outputs())
560                    .map(|output| (output.clone(), build.clone()))
561                    .collect::<Vec<_>>()
562            })
563            .collect()
564    }
565
566    fn create_simple_config(builds: Vec<Build>, default_outputs: &[&str]) -> Arc<Config> {
567        create_pool_config(builds, default_outputs, &[])
568    }
569
570    fn create_pool_config(
571        builds: Vec<Build>,
572        default_outputs: &[&str],
573        pools: &[(&str, usize)],
574    ) -> Arc<Config> {
575        Config::new(
576            create_outputs(builds),
577            default_outputs
578                .iter()
579                .map(|&output| output.into())
580                .collect(),
581            Default::default(),
582            pools
583                .iter()
584                .map(|&(name, depth)| (name.into(), NonZeroUsize::new(depth).unwrap()))
585                .collect(),
586            None,
587        )
588        .into()
589    }
590
591    fn explicit_build(outputs: Vec<Arc<str>>, rule: Rule, inputs: Vec<Arc<str>>) -> Build {
592        Build::new(outputs, vec![], rule.into(), inputs, vec![], None)
593    }
594
595    fn failed_output() -> Output {
596        Output {
597            status: ExitStatus::from_raw(cfg_select! {
598                unix => 1 << 8,
599                windows => 1,
600            }),
601            stdout: vec![],
602            stderr: vec![],
603        }
604    }
605
606    fn limited_pool(name: &str) -> Option<Pool> {
607        Some(Pool::Limited(name.into()))
608    }
609
610    fn pool_build(output: &str, pool: Option<Pool>) -> Build {
611        explicit_build(
612            vec![output.into()],
613            Rule::new(format!("touch {output}"), None).with_pool(pool),
614            vec![],
615        )
616    }
617
618    fn count_metadata_requests(file_system: &FakeFileSystem, path: &str) -> usize {
619        file_system
620            .metadata_requests()
621            .iter()
622            .filter(|request| request.as_path() == Path::new(path))
623            .count()
624    }
625
626    fn create_run_context(
627        command_runner: &FakeCommandRunner,
628        console: &FakeConsole,
629        pools: &[(&str, usize)],
630    ) -> RunContext {
631        RunContext::new(
632            create_context(command_runner, console, &Default::default()),
633            create_pool_config(vec![], &[], pools),
634            BuildGraph::new(&Default::default()),
635            Default::default(),
636            DEFAULT_OPTIONS,
637        )
638    }
639
640    fn create_build_run_context(
641        command_runner: &FakeCommandRunner,
642        file_system: &FakeFileSystem,
643        builds: Vec<Build>,
644    ) -> Arc<RunContext> {
645        RunContext::new(
646            create_context(command_runner, &Default::default(), file_system),
647            create_simple_config(builds, &[]),
648            BuildGraph::new(&Default::default()),
649            Default::default(),
650            DEFAULT_OPTIONS,
651        )
652        .into()
653    }
654
655    #[tokio::test]
656    async fn build_nothing() {
657        let command_runner = FakeCommandRunner::default();
658
659        run(
660            &create_context(&command_runner, &Default::default(), &Default::default()),
661            create_simple_config(vec![], &[]),
662            &[],
663            DEFAULT_OPTIONS,
664        )
665        .await
666        .unwrap();
667
668        assert_eq!(command_runner.commands(), Vec::<String>::new());
669    }
670
671    #[tokio::test]
672    async fn build_default_output() {
673        let command_runner = FakeCommandRunner::default();
674
675        run(
676            &create_context(&command_runner, &Default::default(), &Default::default()),
677            create_simple_config(
678                vec![
679                    explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]),
680                    explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
681                ],
682                &["foo"],
683            ),
684            &[],
685            DEFAULT_OPTIONS,
686        )
687        .await
688        .unwrap();
689
690        assert_eq!(command_runner.commands(), ["touch foo"]);
691    }
692
693    #[tokio::test]
694    async fn build_specified_output() {
695        let command_runner = FakeCommandRunner::default();
696
697        run(
698            &create_context(&command_runner, &Default::default(), &Default::default()),
699            create_simple_config(
700                vec![
701                    explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]),
702                    explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
703                ],
704                &["foo"],
705            ),
706            &["bar".into()],
707            DEFAULT_OPTIONS,
708        )
709        .await
710        .unwrap();
711
712        assert_eq!(command_runner.commands(), ["touch bar"]);
713    }
714
715    #[tokio::test]
716    async fn build_duplicate_outputs() {
717        let command_runner = FakeCommandRunner::default();
718
719        run(
720            &create_context(&command_runner, &Default::default(), &Default::default()),
721            create_simple_config(
722                vec![explicit_build(
723                    vec!["foo".into()],
724                    Rule::new("touch foo", None),
725                    vec![],
726                )],
727                &[],
728            ),
729            &["foo".into(), "foo".into()],
730            DEFAULT_OPTIONS,
731        )
732        .await
733        .unwrap();
734
735        assert_eq!(command_runner.commands(), ["touch foo"]);
736    }
737
738    #[tokio::test]
739    async fn fail_with_unknown_default_output() {
740        assert_eq!(
741            run(
742                &create_context(
743                    &Default::default(),
744                    &Default::default(),
745                    &Default::default()
746                ),
747                create_simple_config(vec![], &["foo"]),
748                &[],
749                DEFAULT_OPTIONS,
750            )
751            .await,
752            Err(BuildError::DefaultOutputNotFound("foo".into()))
753        );
754    }
755
756    #[tokio::test]
757    async fn fail_with_unknown_output() {
758        assert_eq!(
759            run(
760                &create_context(
761                    &Default::default(),
762                    &Default::default(),
763                    &Default::default()
764                ),
765                create_simple_config(vec![], &[]),
766                &["foo".into()],
767                DEFAULT_OPTIONS,
768            )
769            .await,
770            Err(BuildError::OutputNotFound("foo".into()))
771        );
772    }
773
774    #[tokio::test]
775    async fn fail_with_unknown_output_after_known_output() {
776        let command_runner = FakeCommandRunner::default();
777
778        assert_eq!(
779            run(
780                &create_context(&command_runner, &Default::default(), &Default::default()),
781                create_simple_config(
782                    vec![explicit_build(
783                        vec!["foo".into()],
784                        Rule::new("touch foo", None),
785                        vec![],
786                    )],
787                    &[],
788                ),
789                &["foo".into(), "bar".into()],
790                DEFAULT_OPTIONS,
791            )
792            .await,
793            Err(BuildError::OutputNotFound("bar".into()))
794        );
795
796        // Let the runtime run builds spawned before the error if any.
797        yield_now().await;
798
799        assert_eq!(command_runner.commands(), Vec::<String>::new());
800    }
801
802    #[tokio::test]
803    async fn build_shared_input_once() {
804        let command_runner = FakeCommandRunner::default();
805        let file_system = FakeFileSystem::default();
806
807        file_system.write_file("bar", "");
808        file_system.write_file("baz", "");
809
810        run(
811            &create_context(&command_runner, &Default::default(), &file_system),
812            create_simple_config(
813                vec![
814                    explicit_build(
815                        vec!["foo".into()],
816                        Rule::new("touch foo", None),
817                        vec!["bar".into(), "baz".into()],
818                    ),
819                    explicit_build(
820                        vec!["bar".into()],
821                        Rule::new("touch bar", None),
822                        vec!["baz".into()],
823                    ),
824                    explicit_build(vec!["baz".into()], Rule::new("touch baz", None), vec![]),
825                ],
826                &["foo"],
827            ),
828            &[],
829            DEFAULT_OPTIONS,
830        )
831        .await
832        .unwrap();
833
834        assert_eq!(
835            command_runner.commands(),
836            ["touch baz", "touch bar", "touch foo"]
837        );
838    }
839
840    #[tokio::test]
841    async fn build_order_only_input() {
842        let command_runner = FakeCommandRunner::default();
843
844        run(
845            &create_context(&command_runner, &Default::default(), &Default::default()),
846            create_simple_config(
847                vec![
848                    Build::new(
849                        vec!["foo".into()],
850                        vec![],
851                        Rule::new("touch foo", None).into(),
852                        vec![],
853                        vec!["bar".into()],
854                        None,
855                    ),
856                    explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
857                ],
858                &["foo"],
859            ),
860            &[],
861            DEFAULT_OPTIONS,
862        )
863        .await
864        .unwrap();
865
866        assert_eq!(command_runner.commands(), ["touch bar", "touch foo"]);
867    }
868
869    #[tokio::test]
870    async fn build_multiple_outputs_of_build() {
871        let command_runner = FakeCommandRunner::default();
872        let file_system = FakeFileSystem::default();
873
874        file_system.write_file("foo", "");
875        file_system.write_file("bar", "");
876
877        run(
878            &create_context(&command_runner, &Default::default(), &file_system),
879            create_simple_config(
880                vec![
881                    explicit_build(
882                        vec!["foo".into(), "bar".into()],
883                        Rule::new("touch foo bar", None),
884                        vec![],
885                    ),
886                    explicit_build(
887                        vec!["baz".into()],
888                        Rule::new("cat foo bar > baz", None),
889                        vec!["foo".into(), "bar".into()],
890                    ),
891                ],
892                &["baz"],
893            ),
894            &[],
895            DEFAULT_OPTIONS,
896        )
897        .await
898        .unwrap();
899
900        assert_eq!(
901            command_runner.commands(),
902            ["touch foo bar", "cat foo bar > baz"]
903        );
904    }
905
906    #[tokio::test]
907    async fn fail_with_missing_input() {
908        let command_runner = FakeCommandRunner::default();
909
910        assert_eq!(
911            run(
912                &create_context(&command_runner, &Default::default(), &Default::default()),
913                create_simple_config(
914                    vec![explicit_build(
915                        vec!["foo".into()],
916                        Rule::new("cp bar foo", None),
917                        vec!["bar".into()],
918                    )],
919                    &["foo"],
920                ),
921                &[],
922                DEFAULT_OPTIONS,
923            )
924            .await,
925            Err(BuildError::FileNotFound("bar".into()))
926        );
927        assert_eq!(command_runner.commands(), Vec::<String>::new());
928    }
929
930    #[tokio::test]
931    async fn fail_with_missing_order_only_input() {
932        assert_eq!(
933            run(
934                &create_context(
935                    &Default::default(),
936                    &Default::default(),
937                    &Default::default()
938                ),
939                create_simple_config(
940                    vec![Build::new(
941                        vec!["foo".into()],
942                        vec![],
943                        Rule::new("touch foo", None).into(),
944                        vec![],
945                        vec!["bar".into()],
946                        None,
947                    )],
948                    &["foo"],
949                ),
950                &[],
951                DEFAULT_OPTIONS,
952            )
953            .await,
954            Err(BuildError::FileNotFound("bar".into()))
955        );
956    }
957
958    #[tokio::test]
959    async fn report_source_of_missing_input() {
960        let context = create_context(
961            &Default::default(),
962            &Default::default(),
963            &Default::default(),
964        );
965
966        context.database().set_source("foo.o", "foo.c").unwrap();
967
968        assert_eq!(
969            run(
970                &context,
971                create_simple_config(
972                    vec![explicit_build(
973                        vec!["foo".into()],
974                        Rule::new("cc foo.o", None),
975                        vec!["foo.o".into()],
976                    )],
977                    &["foo"],
978                ),
979                &[],
980                DEFAULT_OPTIONS,
981            )
982            .await,
983            Err(BuildError::FileNotFound("foo.c".into()))
984        );
985    }
986
987    #[tokio::test]
988    async fn detect_circular_dependency() {
989        assert_eq!(
990            run(
991                &create_context(
992                    &Default::default(),
993                    &Default::default(),
994                    &Default::default()
995                ),
996                create_simple_config(
997                    vec![explicit_build(
998                        vec!["foo".into()],
999                        Rule::new("cp foo foo", None),
1000                        vec!["foo".into()],
1001                    )],
1002                    &["foo"],
1003                ),
1004                &[],
1005                DEFAULT_OPTIONS,
1006            )
1007            .await,
1008            Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1009                vec!["foo".into()]
1010            )))
1011        );
1012    }
1013
1014    #[tokio::test]
1015    async fn report_sources_in_circular_dependency() {
1016        let context = create_context(
1017            &Default::default(),
1018            &Default::default(),
1019            &Default::default(),
1020        );
1021
1022        context.database().set_source("foo", "baz").unwrap();
1023        context.database().set_source("bar", "baz").unwrap();
1024
1025        assert_eq!(
1026            run(
1027                &context,
1028                create_simple_config(
1029                    vec![
1030                        explicit_build(
1031                            vec!["foo".into()],
1032                            Rule::new("cp bar foo", None),
1033                            vec!["bar".into()],
1034                        ),
1035                        explicit_build(
1036                            vec!["bar".into()],
1037                            Rule::new("cp foo bar", None),
1038                            vec!["foo".into()],
1039                        ),
1040                    ],
1041                    &["foo"],
1042                ),
1043                &[],
1044                DEFAULT_OPTIONS,
1045            )
1046            .await,
1047            Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1048                vec!["baz".into()]
1049            )))
1050        );
1051    }
1052
1053    #[tokio::test]
1054    async fn prepare_output_directory() {
1055        let context = create_context(
1056            &Default::default(),
1057            &Default::default(),
1058            &Default::default(),
1059        );
1060
1061        run(
1062            &context,
1063            create_simple_config(
1064                vec![explicit_build(
1065                    vec!["foo/bar".into()],
1066                    Rule::new("touch foo/bar", None),
1067                    vec![],
1068                )],
1069                &["foo/bar"],
1070            ),
1071            &[],
1072            DEFAULT_OPTIONS,
1073        )
1074        .await
1075        .unwrap();
1076
1077        assert!(context.file_system().exists("foo".as_ref()).await.unwrap());
1078    }
1079
1080    #[tokio::test]
1081    async fn record_output_with_source() {
1082        let file_system = FakeFileSystem::default();
1083        let context = create_context(&Default::default(), &Default::default(), &file_system);
1084
1085        file_system.write_file("foo.c", "");
1086
1087        run(
1088            &context,
1089            Config::new(
1090                create_outputs(vec![explicit_build(
1091                    vec!["foo.o".into()],
1092                    Rule::new("cc foo.c", None),
1093                    vec!["foo.c".into()],
1094                )]),
1095                ["foo.o".into()].into_iter().collect(),
1096                [("foo.o".into(), "foo.c".into())].into_iter().collect(),
1097                Default::default(),
1098                None,
1099            )
1100            .into(),
1101            &[],
1102            DEFAULT_OPTIONS,
1103        )
1104        .await
1105        .unwrap();
1106
1107        assert_eq!(context.database().get_outputs().unwrap(), ["foo.o"]);
1108        assert_eq!(
1109            context.database().get_source("foo.o").unwrap(),
1110            Some("foo.c".into())
1111        );
1112    }
1113
1114    #[tokio::test]
1115    async fn skip_up_to_date_build() {
1116        let command_runner = FakeCommandRunner::default();
1117        let file_system = FakeFileSystem::default();
1118        let context = create_context(&command_runner, &Default::default(), &file_system);
1119        let config = create_simple_config(
1120            vec![explicit_build(
1121                vec!["foo".into()],
1122                Rule::new("cp bar foo", None),
1123                vec!["bar".into()],
1124            )],
1125            &["foo"],
1126        );
1127
1128        file_system.write_file("foo", "");
1129        file_system.write_file("bar", "");
1130
1131        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1132            .await
1133            .unwrap();
1134        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1135
1136        assert_eq!(command_runner.commands(), ["cp bar foo"]);
1137    }
1138
1139    #[tokio::test]
1140    async fn skip_build_on_timestamp_update_of_input() {
1141        let command_runner = FakeCommandRunner::default();
1142        let file_system = FakeFileSystem::default();
1143        let context = create_context(&command_runner, &Default::default(), &file_system);
1144        let config = create_simple_config(
1145            vec![explicit_build(
1146                vec!["foo".into()],
1147                Rule::new("cp bar foo", None),
1148                vec!["bar".into()],
1149            )],
1150            &["foo"],
1151        );
1152
1153        file_system.write_file("foo", "");
1154        file_system.write_file("bar", "");
1155
1156        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1157            .await
1158            .unwrap();
1159
1160        file_system.write_file("bar", "");
1161
1162        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1163
1164        assert_eq!(command_runner.commands(), ["cp bar foo"]);
1165    }
1166
1167    #[tokio::test]
1168    async fn rebuild_on_content_update_of_input() {
1169        let command_runner = FakeCommandRunner::default();
1170        let file_system = FakeFileSystem::default();
1171        let context = create_context(&command_runner, &Default::default(), &file_system);
1172        let config = create_simple_config(
1173            vec![explicit_build(
1174                vec!["foo".into()],
1175                Rule::new("cp bar foo", None),
1176                vec!["bar".into()],
1177            )],
1178            &["foo"],
1179        );
1180
1181        file_system.write_file("foo", "");
1182        file_system.write_file("bar", "");
1183
1184        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1185            .await
1186            .unwrap();
1187
1188        file_system.write_file("bar", "bar");
1189
1190        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1191
1192        assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1193    }
1194
1195    #[tokio::test]
1196    async fn rebuild_on_update_of_phony_input() {
1197        let command_runner = FakeCommandRunner::default();
1198        let file_system = FakeFileSystem::default();
1199        let context = create_context(&command_runner, &Default::default(), &file_system);
1200        let config = create_simple_config(
1201            vec![
1202                explicit_build(
1203                    vec!["foo".into()],
1204                    Rule::new("cp bar foo", None),
1205                    vec!["bar".into()],
1206                ),
1207                Build::new(
1208                    vec!["bar".into()],
1209                    vec![],
1210                    None,
1211                    vec!["baz".into()],
1212                    vec![],
1213                    None,
1214                ),
1215            ],
1216            &["foo"],
1217        );
1218
1219        file_system.write_file("foo", "");
1220        file_system.write_file("baz", "");
1221
1222        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1223            .await
1224            .unwrap();
1225
1226        file_system.write_file("baz", "baz");
1227
1228        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1229
1230        assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1231    }
1232
1233    #[tokio::test]
1234    async fn rebuild_missing_output() {
1235        let command_runner = FakeCommandRunner::default();
1236        let file_system = FakeFileSystem::default();
1237        let context = create_context(&command_runner, &Default::default(), &file_system);
1238        let config = create_simple_config(
1239            vec![explicit_build(
1240                vec!["foo".into()],
1241                Rule::new("cp bar foo", None),
1242                vec!["bar".into()],
1243            )],
1244            &["foo"],
1245        );
1246
1247        file_system.write_file("bar", "");
1248
1249        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1250            .await
1251            .unwrap();
1252        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1253
1254        assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1255    }
1256
1257    #[tokio::test]
1258    async fn rebuild_missing_implicit_output() {
1259        let command_runner = FakeCommandRunner::default();
1260        let file_system = FakeFileSystem::default();
1261        let context = create_context(&command_runner, &Default::default(), &file_system);
1262        let config = create_simple_config(
1263            vec![Build::new(
1264                vec!["foo".into()],
1265                vec!["baz".into()],
1266                Rule::new("cp bar foo", None).into(),
1267                vec!["bar".into()],
1268                vec![],
1269                None,
1270            )],
1271            &["foo"],
1272        );
1273
1274        file_system.write_file("foo", "");
1275        file_system.write_file("bar", "");
1276
1277        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1278            .await
1279            .unwrap();
1280        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1281
1282        assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1283    }
1284
1285    #[tokio::test]
1286    async fn rerun_failed_build() {
1287        let command_runner =
1288            FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1289        let file_system = FakeFileSystem::default();
1290        let context = create_context(&command_runner, &Default::default(), &file_system);
1291        let config = create_simple_config(
1292            vec![explicit_build(
1293                vec!["foo".into()],
1294                Rule::new("exit 1", None),
1295                vec![],
1296            )],
1297            &["foo"],
1298        );
1299
1300        file_system.write_file("foo", "");
1301
1302        assert_eq!(
1303            run(&context, config.clone(), &[], DEFAULT_OPTIONS).await,
1304            Err(BuildError::Build)
1305        );
1306        assert_eq!(
1307            run(&context, config, &[], DEFAULT_OPTIONS).await,
1308            Err(BuildError::Build)
1309        );
1310        assert_eq!(command_runner.commands(), ["exit 1", "exit 1"]);
1311    }
1312
1313    #[tokio::test]
1314    async fn invalidate_file_cache_of_outputs_after_command() {
1315        let command_runner = FakeCommandRunner::default();
1316        let file_system = FakeFileSystem::default();
1317        let context = create_build_run_context(&command_runner, &file_system, vec![]);
1318        let build = Build::new(
1319            vec!["foo".into()],
1320            vec!["bar".into()],
1321            Rule::new("touch foo bar", None).into(),
1322            vec![],
1323            vec![],
1324            None,
1325        )
1326        .into();
1327        let mut future = pin!(run_build(context.clone(), &build));
1328
1329        file_system.write_file("foo", "1");
1330        file_system.write_file("bar", "1");
1331
1332        assert!(poll!(&mut future).is_pending());
1333
1334        // Let the runtime run the build until its command suspends.
1335        yield_now().await;
1336
1337        assert_eq!(command_runner.commands(), ["touch foo bar"]);
1338
1339        let foo_hash = context.file_cache().content_hash("foo".as_ref()).await;
1340        let bar_hash = context.file_cache().content_hash("bar".as_ref()).await;
1341
1342        file_system.write_file("foo", "2");
1343        file_system.write_file("bar", "2");
1344        future.await.unwrap();
1345
1346        assert_ne!(
1347            context.file_cache().content_hash("foo".as_ref()).await,
1348            foo_hash
1349        );
1350        assert_ne!(
1351            context.file_cache().content_hash("bar".as_ref()).await,
1352            bar_hash
1353        );
1354    }
1355
1356    #[tokio::test]
1357    async fn invalidate_file_cache_of_output_after_failed_command() {
1358        let command_runner =
1359            FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1360        let file_system = FakeFileSystem::default();
1361        let context = create_build_run_context(&command_runner, &file_system, vec![]);
1362        let build = explicit_build(vec!["foo".into()], Rule::new("exit 1", None), vec![]).into();
1363        let mut future = pin!(run_build(context.clone(), &build));
1364
1365        file_system.write_file("foo", "1");
1366
1367        assert!(poll!(&mut future).is_pending());
1368
1369        // Let the runtime run the build until its command suspends.
1370        yield_now().await;
1371
1372        assert_eq!(command_runner.commands(), ["exit 1"]);
1373
1374        let hash = context.file_cache().content_hash("foo".as_ref()).await;
1375
1376        file_system.write_file("foo", "2");
1377
1378        assert_eq!(future.await, Err(BuildError::Build));
1379        assert_ne!(
1380            context.file_cache().content_hash("foo".as_ref()).await,
1381            hash
1382        );
1383    }
1384
1385    #[tokio::test]
1386    async fn invalidate_file_cache_of_phony_output_after_inputs() {
1387        let command_runner = FakeCommandRunner::default();
1388        let file_system = FakeFileSystem::default();
1389        let build = Build::new(
1390            vec!["foo".into()],
1391            vec![],
1392            None,
1393            vec!["bar".into()],
1394            vec![],
1395            None,
1396        );
1397        let context = create_build_run_context(
1398            &command_runner,
1399            &file_system,
1400            vec![
1401                build.clone(),
1402                explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
1403            ],
1404        );
1405        let build = build.into();
1406        let mut future = pin!(run_build(context.clone(), &build));
1407
1408        file_system.write_file("foo", "1");
1409        file_system.write_file("bar", "");
1410
1411        assert!(poll!(&mut future).is_pending());
1412
1413        // Let the runtime run the builds until the command of the input suspends.
1414        yield_now().await;
1415
1416        assert_eq!(command_runner.commands(), ["touch bar"]);
1417
1418        let hash = context.file_cache().content_hash("foo".as_ref()).await;
1419
1420        file_system.write_file("foo", "2");
1421        future.await.unwrap();
1422
1423        assert_ne!(
1424            context.file_cache().content_hash("foo".as_ref()).await,
1425            hash
1426        );
1427    }
1428
1429    #[tokio::test]
1430    async fn keep_file_cache_of_other_file_after_command() {
1431        let command_runner = FakeCommandRunner::default();
1432        let file_system = FakeFileSystem::default();
1433        let context = create_build_run_context(&command_runner, &file_system, vec![]);
1434
1435        file_system.write_file("bar", "1");
1436
1437        let hash = context.file_cache().content_hash("bar".as_ref()).await;
1438
1439        file_system.write_file("bar", "2");
1440        run_build(
1441            context.clone(),
1442            &explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]).into(),
1443        )
1444        .await
1445        .unwrap();
1446
1447        assert_eq!(command_runner.commands(), ["touch foo"]);
1448        assert_eq!(
1449            context.file_cache().content_hash("bar".as_ref()).await,
1450            hash
1451        );
1452    }
1453
1454    #[tokio::test]
1455    async fn query_output_while_building_input() {
1456        let file_system = FakeFileSystem::default();
1457
1458        file_system.write_file("foo", "");
1459        file_system.write_file("bar", "");
1460        file_system.write_file("baz", "");
1461
1462        run(
1463            &create_context(&Default::default(), &Default::default(), &file_system),
1464            create_simple_config(
1465                vec![
1466                    explicit_build(
1467                        vec!["foo".into()],
1468                        Rule::new("cp bar foo", None),
1469                        vec!["bar".into()],
1470                    ),
1471                    explicit_build(
1472                        vec!["bar".into()],
1473                        Rule::new("cp baz bar", None),
1474                        vec!["baz".into()],
1475                    ),
1476                ],
1477                &["foo"],
1478            ),
1479            &[],
1480            DEFAULT_OPTIONS,
1481        )
1482        .await
1483        .unwrap();
1484
1485        assert_eq!(file_system.metadata_requests().first(), Some(&"foo".into()));
1486    }
1487
1488    #[tokio::test]
1489    async fn query_output_once_for_up_to_date_build() {
1490        let file_system = FakeFileSystem::default();
1491        let context = create_context(&Default::default(), &Default::default(), &file_system);
1492        let config = create_simple_config(
1493            vec![
1494                explicit_build(
1495                    vec!["foo".into()],
1496                    Rule::new("cp bar foo", None),
1497                    vec!["bar".into()],
1498                ),
1499                explicit_build(
1500                    vec!["bar".into()],
1501                    Rule::new("cp baz bar", None),
1502                    vec!["baz".into()],
1503                ),
1504            ],
1505            &["foo"],
1506        );
1507
1508        file_system.write_file("foo", "");
1509        file_system.write_file("bar", "");
1510        file_system.write_file("baz", "");
1511
1512        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1513            .await
1514            .unwrap();
1515
1516        let count = count_metadata_requests(&file_system, "bar");
1517
1518        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1519
1520        assert_eq!(count_metadata_requests(&file_system, "bar"), count + 1);
1521    }
1522
1523    #[tokio::test]
1524    async fn query_implicit_output_once_for_up_to_date_build() {
1525        let file_system = FakeFileSystem::default();
1526        let context = create_context(&Default::default(), &Default::default(), &file_system);
1527        let config = create_simple_config(
1528            vec![
1529                explicit_build(
1530                    vec!["foo".into()],
1531                    Rule::new("cp qux foo", None),
1532                    vec!["qux".into()],
1533                ),
1534                Build::new(
1535                    vec!["bar".into()],
1536                    vec!["qux".into()],
1537                    Rule::new("cp baz bar", None).into(),
1538                    vec!["baz".into()],
1539                    vec![],
1540                    None,
1541                ),
1542            ],
1543            &["foo"],
1544        );
1545
1546        file_system.write_file("foo", "");
1547        file_system.write_file("bar", "");
1548        file_system.write_file("baz", "");
1549        file_system.write_file("qux", "");
1550
1551        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1552            .await
1553            .unwrap();
1554
1555        let count = count_metadata_requests(&file_system, "qux");
1556
1557        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1558
1559        assert_eq!(count_metadata_requests(&file_system, "qux"), count + 1);
1560    }
1561
1562    #[tokio::test]
1563    async fn query_output_again_on_timestamp_update_of_input() {
1564        let file_system = FakeFileSystem::default();
1565        let context = create_context(&Default::default(), &Default::default(), &file_system);
1566        let config = create_simple_config(
1567            vec![
1568                explicit_build(
1569                    vec!["foo".into()],
1570                    Rule::new("cp bar foo", None),
1571                    vec!["bar".into()],
1572                ),
1573                explicit_build(
1574                    vec!["bar".into()],
1575                    Rule::new("cp baz bar", None),
1576                    vec!["baz".into()],
1577                ),
1578            ],
1579            &["foo"],
1580        );
1581
1582        file_system.write_file("foo", "");
1583        file_system.write_file("bar", "");
1584        file_system.write_file("baz", "");
1585
1586        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1587            .await
1588            .unwrap();
1589
1590        file_system.write_file("baz", "");
1591
1592        let count = count_metadata_requests(&file_system, "bar");
1593
1594        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1595
1596        assert_eq!(count_metadata_requests(&file_system, "bar"), count + 2);
1597    }
1598
1599    #[tokio::test]
1600    async fn query_output_again_after_command() {
1601        let file_system = FakeFileSystem::default();
1602
1603        file_system.write_file("foo", "");
1604        file_system.write_file("bar", "");
1605        file_system.write_file("baz", "");
1606
1607        run(
1608            &create_context(&Default::default(), &Default::default(), &file_system),
1609            create_simple_config(
1610                vec![
1611                    explicit_build(
1612                        vec!["foo".into()],
1613                        Rule::new("cp bar foo", None),
1614                        vec!["bar".into()],
1615                    ),
1616                    explicit_build(
1617                        vec!["bar".into()],
1618                        Rule::new("cp baz bar", None),
1619                        vec!["baz".into()],
1620                    ),
1621                ],
1622                &["foo"],
1623            ),
1624            &[],
1625            DEFAULT_OPTIONS,
1626        )
1627        .await
1628        .unwrap();
1629
1630        assert_eq!(count_metadata_requests(&file_system, "bar"), 2);
1631    }
1632
1633    #[tokio::test]
1634    async fn query_output_again_for_up_to_date_phony_build() {
1635        let file_system = FakeFileSystem::default();
1636        let context = create_context(&Default::default(), &Default::default(), &file_system);
1637        let config = create_simple_config(
1638            vec![
1639                explicit_build(
1640                    vec!["foo.o".into()],
1641                    Rule::new("cc foo.c", None).with_header_dependency(Some(
1642                        HeaderDependency::Make {
1643                            path: "foo.d".into(),
1644                        },
1645                    )),
1646                    vec!["foo.c".into()],
1647                ),
1648                Build::new(
1649                    vec!["foo.h".into()],
1650                    vec![],
1651                    None,
1652                    vec!["foo.h.in".into()],
1653                    vec![],
1654                    None,
1655                ),
1656            ],
1657            &["foo.o", "foo.h"],
1658        );
1659
1660        file_system.write_file("foo.o", "");
1661        file_system.write_file("foo.c", "");
1662        file_system.write_file("foo.h", "");
1663        file_system.write_file("foo.h.in", "");
1664        file_system.write_file("foo.d", "foo.o: foo.h\n");
1665
1666        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1667            .await
1668            .unwrap();
1669
1670        let count = count_metadata_requests(&file_system, "foo.h");
1671
1672        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1673
1674        assert_eq!(count_metadata_requests(&file_system, "foo.h"), count + 2);
1675    }
1676
1677    #[tokio::test]
1678    async fn fail_with_output_metadata_error() {
1679        let command_runner = FakeCommandRunner::default();
1680        let file_system = FakeFileSystem::default();
1681
1682        file_system.write_file("bar", "");
1683        file_system.fail_metadata("foo");
1684
1685        assert_eq!(
1686            run(
1687                &create_context(&command_runner, &Default::default(), &file_system),
1688                create_simple_config(
1689                    vec![explicit_build(
1690                        vec!["foo".into()],
1691                        Rule::new("cp bar foo", None),
1692                        vec!["bar".into()],
1693                    )],
1694                    &["foo"],
1695                ),
1696                &[],
1697                DEFAULT_OPTIONS,
1698            )
1699            .await,
1700            Err(BuildError::File(FileError::new("metadata failure")))
1701        );
1702        assert_eq!(command_runner.commands(), Vec::<String>::new());
1703    }
1704
1705    #[tokio::test]
1706    async fn write_command_output() {
1707        let console = FakeConsole::default();
1708
1709        run(
1710            &create_context(
1711                &FakeCommandRunner::new(
1712                    [(
1713                        "touch foo".into(),
1714                        Output {
1715                            status: ExitStatus::default(),
1716                            stdout: b"bar\n".into(),
1717                            stderr: b"baz\n".into(),
1718                        },
1719                    )]
1720                    .into_iter()
1721                    .collect(),
1722                ),
1723                &console,
1724                &Default::default(),
1725            ),
1726            create_simple_config(
1727                vec![explicit_build(
1728                    vec!["foo".into()],
1729                    Rule::new("touch foo", Some("build foo".into())),
1730                    vec![],
1731                )],
1732                &["foo"],
1733            ),
1734            &[],
1735            DEFAULT_OPTIONS,
1736        )
1737        .await
1738        .unwrap();
1739
1740        assert_eq!(console.stdout(), "bar\n");
1741        assert_eq!(console.stderr(), "build foo\nbaz\n");
1742    }
1743
1744    #[tokio::test]
1745    async fn write_debug_log_of_failed_build() {
1746        let console = FakeConsole::default();
1747
1748        assert_eq!(
1749            run(
1750                &create_context(
1751                    &FakeCommandRunner::new(
1752                        [("exit 1".into(), failed_output())].into_iter().collect(),
1753                    ),
1754                    &console,
1755                    &Default::default(),
1756                ),
1757                create_simple_config(
1758                    vec![explicit_build(
1759                        vec!["foo".into()],
1760                        Rule::new("exit 1", None),
1761                        vec![],
1762                    )],
1763                    &["foo"],
1764                ),
1765                &[],
1766                RunOptions {
1767                    debug: true,
1768                    profile: false,
1769                },
1770            )
1771            .await,
1772            Err(BuildError::Build)
1773        );
1774        assert_eq!(
1775            console.stderr(),
1776            "turtle: command: exit 1\nturtle: exit status: 1\n"
1777        );
1778    }
1779
1780    #[tokio::test]
1781    async fn write_profile_log() {
1782        let console = FakeConsole::default();
1783
1784        run(
1785            &create_context(&Default::default(), &console, &Default::default()),
1786            create_simple_config(
1787                vec![explicit_build(
1788                    vec!["foo".into()],
1789                    Rule::new("touch foo", None),
1790                    vec![],
1791                )],
1792                &["foo"],
1793            ),
1794            &[],
1795            RunOptions {
1796                debug: false,
1797                profile: true,
1798            },
1799        )
1800        .await
1801        .unwrap();
1802
1803        assert!(
1804            Regex::new(r"^turtle: duration: \d+ ms\n$")
1805                .unwrap()
1806                .is_match(&console.stderr())
1807        );
1808    }
1809
1810    #[tokio::test]
1811    async fn rebuild_on_update_of_header_dependency() {
1812        let command_runner = FakeCommandRunner::default();
1813        let file_system = FakeFileSystem::default();
1814        let context = create_context(&command_runner, &Default::default(), &file_system);
1815        let config = create_simple_config(
1816            vec![explicit_build(
1817                vec!["foo.o".into()],
1818                Rule::new("cc foo.c", None).with_header_dependency(Some(HeaderDependency::Make {
1819                    path: "foo.d".into(),
1820                })),
1821                vec!["foo.c".into()],
1822            )],
1823            &["foo.o"],
1824        );
1825
1826        file_system.write_file("foo.o", "");
1827        file_system.write_file("foo.c", "");
1828        file_system.write_file("foo.h", "");
1829        file_system.write_file("foo.d", "foo.o: foo.h\n");
1830
1831        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1832            .await
1833            .unwrap();
1834        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1835            .await
1836            .unwrap();
1837
1838        file_system.write_file("foo.h", "foo");
1839
1840        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1841
1842        assert_eq!(command_runner.commands(), ["cc foo.c", "cc foo.c"]);
1843    }
1844
1845    #[tokio::test]
1846    async fn read_and_remove_gcc_depfile() {
1847        let file_system = FakeFileSystem::default();
1848        let context = create_context(&Default::default(), &Default::default(), &file_system);
1849        let build = explicit_build(
1850            vec!["foo.o".into()],
1851            Rule::new("cc foo.c", None).with_header_dependency(Some(HeaderDependency::Gcc {
1852                path: "foo.d".into(),
1853            })),
1854            vec!["foo.c".into()],
1855        );
1856
1857        file_system.write_file("foo.c", "");
1858        file_system.write_file("foo.h", "");
1859        file_system.write_file("foo.d", "foo.o: foo.h\n");
1860
1861        run(
1862            &context,
1863            create_simple_config(vec![build.clone()], &["foo.o"]),
1864            &[],
1865            DEFAULT_OPTIONS,
1866        )
1867        .await
1868        .unwrap();
1869
1870        assert_eq!(
1871            context
1872                .database()
1873                .get_header_dependencies(build.id())
1874                .unwrap(),
1875            ["foo.h"]
1876        );
1877        assert!(
1878            !context
1879                .file_system()
1880                .exists("foo.d".as_ref())
1881                .await
1882                .unwrap()
1883        );
1884    }
1885
1886    #[tokio::test]
1887    async fn read_msvc_header_dependencies() {
1888        let console = FakeConsole::default();
1889        let file_system = FakeFileSystem::default();
1890        let context = create_context(
1891            &FakeCommandRunner::new(
1892                [(
1893                    "cl foo.c".into(),
1894                    Output {
1895                        status: ExitStatus::default(),
1896                        stdout: b"Note: including file: foo.h\nfoo.c\n".into(),
1897                        stderr: vec![],
1898                    },
1899                )]
1900                .into_iter()
1901                .collect(),
1902            ),
1903            &console,
1904            &file_system,
1905        );
1906        let build = explicit_build(
1907            vec!["foo.obj".into()],
1908            Rule::new("cl foo.c", None).with_header_dependency(Some(HeaderDependency::Msvc {
1909                prefix: "Note: including file: ".into(),
1910            })),
1911            vec!["foo.c".into()],
1912        );
1913
1914        file_system.write_file("foo.c", "");
1915        file_system.write_file("foo.h", "");
1916
1917        run(
1918            &context,
1919            create_simple_config(vec![build.clone()], &["foo.obj"]),
1920            &[],
1921            DEFAULT_OPTIONS,
1922        )
1923        .await
1924        .unwrap();
1925
1926        assert_eq!(
1927            context
1928                .database()
1929                .get_header_dependencies(build.id())
1930                .unwrap(),
1931            ["foo.h"]
1932        );
1933        assert_eq!(console.stdout(), "foo.c\n");
1934    }
1935
1936    #[tokio::test]
1937    async fn build_generated_header_dependency() {
1938        let command_runner = FakeCommandRunner::default();
1939        let file_system = FakeFileSystem::default();
1940        let context = create_context(&command_runner, &Default::default(), &file_system);
1941        let build = explicit_build(
1942            vec!["foo.o".into()],
1943            Rule::new("cc foo.c", None),
1944            vec!["foo.c".into()],
1945        );
1946
1947        context
1948            .database()
1949            .set_header_dependencies(build.id(), &["foo.h".into()])
1950            .unwrap();
1951        file_system.write_file("foo.c", "");
1952        file_system.write_file("foo.h", "");
1953
1954        run(
1955            &context,
1956            create_simple_config(
1957                vec![
1958                    build,
1959                    explicit_build(vec!["foo.h".into()], Rule::new("touch foo.h", None), vec![]),
1960                ],
1961                &["foo.o"],
1962            ),
1963            &[],
1964            DEFAULT_OPTIONS,
1965        )
1966        .await
1967        .unwrap();
1968
1969        assert_eq!(command_runner.commands(), ["touch foo.h", "cc foo.c"]);
1970    }
1971
1972    #[tokio::test]
1973    async fn ignore_missing_header_dependency() {
1974        let file_system = FakeFileSystem::default();
1975        let context = create_context(&Default::default(), &Default::default(), &file_system);
1976        let build = explicit_build(
1977            vec!["foo.o".into()],
1978            Rule::new("cc foo.c", None),
1979            vec!["foo.c".into()],
1980        );
1981
1982        context
1983            .database()
1984            .set_header_dependencies(build.id(), &["foo.h".into()])
1985            .unwrap();
1986        file_system.write_file("foo.c", "");
1987
1988        run(
1989            &context,
1990            create_simple_config(vec![build], &["foo.o"]),
1991            &[],
1992            DEFAULT_OPTIONS,
1993        )
1994        .await
1995        .unwrap();
1996    }
1997
1998    #[tokio::test]
1999    async fn read_header_dependencies_once() {
2000        let database = FakeDatabase::default();
2001        let file_system = FakeFileSystem::default();
2002        let context = Arc::new(Context::new(
2003            FakeCommandRunner::default(),
2004            Mutex::new(FakeConsole::default()).into(),
2005            database.clone(),
2006            file_system.clone(),
2007        ));
2008        let build = explicit_build(
2009            vec!["foo.o".into()],
2010            Rule::new("cc foo.c", None),
2011            vec!["foo.c".into()],
2012        );
2013
2014        context
2015            .database()
2016            .set_header_dependencies(build.id(), &["foo.h".into()])
2017            .unwrap();
2018        file_system.write_file("foo.c", "");
2019        file_system.write_file("foo.h", "");
2020
2021        run(
2022            &context,
2023            create_simple_config(vec![build.clone()], &["foo.o"]),
2024            &[],
2025            DEFAULT_OPTIONS,
2026        )
2027        .await
2028        .unwrap();
2029
2030        assert_eq!(database.header_dependency_requests(), [build.id()]);
2031    }
2032
2033    #[tokio::test]
2034    async fn detect_circular_header_dependency() {
2035        let context = create_context(
2036            &Default::default(),
2037            &Default::default(),
2038            &Default::default(),
2039        );
2040        let build = explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]);
2041
2042        context
2043            .database()
2044            .set_header_dependencies(build.id(), &["foo".into()])
2045            .unwrap();
2046
2047        assert_eq!(
2048            run(
2049                &context,
2050                create_simple_config(vec![build], &["foo"]),
2051                &[],
2052                DEFAULT_OPTIONS,
2053            )
2054            .await,
2055            Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
2056                vec!["foo".into()]
2057            )))
2058        );
2059    }
2060
2061    #[tokio::test]
2062    async fn build_dynamic_input() {
2063        let command_runner = FakeCommandRunner::default();
2064        let file_system = FakeFileSystem::default();
2065
2066        file_system.write_file(
2067            "foo.dd",
2068            "ninja_dyndep_version = 1\nbuild foo: dyndep | bar\n",
2069        );
2070        file_system.write_file("bar", "");
2071
2072        run(
2073            &create_context(&command_runner, &Default::default(), &file_system),
2074            create_simple_config(
2075                vec![
2076                    Build::new(
2077                        vec!["foo".into()],
2078                        vec![],
2079                        Rule::new("touch foo", None).into(),
2080                        vec![],
2081                        vec!["foo.dd".into()],
2082                        Some("foo.dd".into()),
2083                    ),
2084                    explicit_build(
2085                        vec!["foo.dd".into()],
2086                        Rule::new("touch foo.dd", None),
2087                        vec![],
2088                    ),
2089                    explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
2090                ],
2091                &["foo"],
2092            ),
2093            &[],
2094            DEFAULT_OPTIONS,
2095        )
2096        .await
2097        .unwrap();
2098
2099        assert_eq!(
2100            command_runner.commands(),
2101            ["touch foo.dd", "touch bar", "touch foo"]
2102        );
2103    }
2104
2105    #[tokio::test]
2106    async fn build_dynamic_input_declared_for_implicit_output() {
2107        let command_runner = FakeCommandRunner::default();
2108        let file_system = FakeFileSystem::default();
2109
2110        file_system.write_file(
2111            "foo.dd",
2112            "ninja_dyndep_version = 1\nbuild baz: dyndep | bar\n",
2113        );
2114        file_system.write_file("bar", "");
2115
2116        run(
2117            &create_context(&command_runner, &Default::default(), &file_system),
2118            create_simple_config(
2119                vec![
2120                    Build::new(
2121                        vec!["foo".into()],
2122                        vec!["baz".into()],
2123                        Rule::new("touch foo", None).into(),
2124                        vec![],
2125                        vec!["foo.dd".into()],
2126                        Some("foo.dd".into()),
2127                    ),
2128                    explicit_build(
2129                        vec!["foo.dd".into()],
2130                        Rule::new("touch foo.dd", None),
2131                        vec![],
2132                    ),
2133                    explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
2134                ],
2135                &["foo"],
2136            ),
2137            &[],
2138            DEFAULT_OPTIONS,
2139        )
2140        .await
2141        .unwrap();
2142
2143        assert_eq!(
2144            command_runner.commands(),
2145            ["touch foo.dd", "touch bar", "touch foo"]
2146        );
2147    }
2148
2149    #[tokio::test]
2150    async fn fail_with_missing_dynamic_dependency() {
2151        let file_system = FakeFileSystem::default();
2152        let build = Build::new(
2153            vec!["foo".into()],
2154            vec![],
2155            Rule::new("touch foo", None).into(),
2156            vec![],
2157            vec![],
2158            Some("foo.dd".into()),
2159        );
2160
2161        file_system.write_file("foo.dd", "ninja_dyndep_version = 1\n");
2162
2163        assert_eq!(
2164            run(
2165                &create_context(&Default::default(), &Default::default(), &file_system),
2166                create_simple_config(vec![build.clone()], &["foo"]),
2167                &[],
2168                DEFAULT_OPTIONS,
2169            )
2170            .await,
2171            Err(BuildError::DynamicDependencyNotFound(build.into()))
2172        );
2173    }
2174
2175    #[tokio::test]
2176    async fn fail_with_unknown_output_in_dynamic_dependency() {
2177        let file_system = FakeFileSystem::default();
2178
2179        file_system.write_file(
2180            "foo.dd",
2181            "ninja_dyndep_version = 1\nbuild bar: dyndep | baz\n",
2182        );
2183
2184        assert_eq!(
2185            run(
2186                &create_context(&Default::default(), &Default::default(), &file_system),
2187                create_simple_config(
2188                    vec![Build::new(
2189                        vec!["foo".into()],
2190                        vec![],
2191                        Rule::new("touch foo", None).into(),
2192                        vec![],
2193                        vec![],
2194                        Some("foo.dd".into()),
2195                    )],
2196                    &["foo"],
2197                ),
2198                &[],
2199                DEFAULT_OPTIONS,
2200            )
2201            .await,
2202            Err(BuildError::BuildGraph(BuildGraphError::OutputNotFound(
2203                "bar".into()
2204            )))
2205        );
2206    }
2207
2208    #[tokio::test]
2209    async fn detect_circular_dynamic_dependency() {
2210        let file_system = FakeFileSystem::default();
2211
2212        file_system.write_file(
2213            "foo.dd",
2214            "ninja_dyndep_version = 1\nbuild foo: dyndep | foo\n",
2215        );
2216
2217        assert_eq!(
2218            run(
2219                &create_context(&Default::default(), &Default::default(), &file_system),
2220                create_simple_config(
2221                    vec![Build::new(
2222                        vec!["foo".into()],
2223                        vec![],
2224                        Rule::new("touch foo", None).into(),
2225                        vec![],
2226                        vec![],
2227                        Some("foo.dd".into()),
2228                    )],
2229                    &["foo"],
2230                ),
2231                &[],
2232                DEFAULT_OPTIONS,
2233            )
2234            .await,
2235            Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
2236                vec!["foo".into()]
2237            )))
2238        );
2239    }
2240
2241    #[tokio::test]
2242    async fn run_builds_concurrently() {
2243        let command_runner = FakeCommandRunner::default();
2244
2245        run(
2246            &create_context(&command_runner, &Default::default(), &Default::default()),
2247            create_simple_config(
2248                vec![
2249                    pool_build("foo", None),
2250                    pool_build("bar", None),
2251                    pool_build("baz", None),
2252                ],
2253                &["foo", "bar", "baz"],
2254            ),
2255            &[],
2256            DEFAULT_OPTIONS,
2257        )
2258        .await
2259        .unwrap();
2260
2261        assert_eq!(command_runner.max_concurrency(), 3);
2262    }
2263
2264    #[tokio::test]
2265    async fn limit_concurrency_of_builds_in_pool() {
2266        let command_runner = FakeCommandRunner::default();
2267
2268        run(
2269            &create_context(&command_runner, &Default::default(), &Default::default()),
2270            create_pool_config(
2271                vec![
2272                    pool_build("foo", limited_pool("qux")),
2273                    pool_build("bar", limited_pool("qux")),
2274                    pool_build("baz", limited_pool("qux")),
2275                ],
2276                &["foo", "bar", "baz"],
2277                &[("qux", 1)],
2278            ),
2279            &[],
2280            DEFAULT_OPTIONS,
2281        )
2282        .await
2283        .unwrap();
2284
2285        assert_eq!(command_runner.commands().len(), 3);
2286        assert_eq!(command_runner.max_concurrency(), 1);
2287    }
2288
2289    #[tokio::test]
2290    async fn limit_concurrency_of_builds_in_pool_with_depth_of_two() {
2291        let command_runner = FakeCommandRunner::default();
2292
2293        run(
2294            &create_context(&command_runner, &Default::default(), &Default::default()),
2295            create_pool_config(
2296                vec![
2297                    pool_build("foo", limited_pool("quux")),
2298                    pool_build("bar", limited_pool("quux")),
2299                    pool_build("baz", limited_pool("quux")),
2300                    pool_build("qux", limited_pool("quux")),
2301                ],
2302                &["foo", "bar", "baz", "qux"],
2303                &[("quux", 2)],
2304            ),
2305            &[],
2306            DEFAULT_OPTIONS,
2307        )
2308        .await
2309        .unwrap();
2310
2311        assert_eq!(command_runner.commands().len(), 4);
2312        assert_eq!(command_runner.max_concurrency(), 2);
2313    }
2314
2315    #[tokio::test]
2316    async fn run_builds_in_different_pools_concurrently() {
2317        let command_runner = FakeCommandRunner::default();
2318
2319        run(
2320            &create_context(&command_runner, &Default::default(), &Default::default()),
2321            create_pool_config(
2322                vec![
2323                    pool_build("foo", limited_pool("baz")),
2324                    pool_build("bar", limited_pool("qux")),
2325                ],
2326                &["foo", "bar"],
2327                &[("baz", 1), ("qux", 1)],
2328            ),
2329            &[],
2330            DEFAULT_OPTIONS,
2331        )
2332        .await
2333        .unwrap();
2334
2335        assert_eq!(command_runner.max_concurrency(), 2);
2336    }
2337
2338    #[tokio::test]
2339    async fn run_build_in_pool_of_huge_depth() {
2340        let command_runner = FakeCommandRunner::default();
2341
2342        run(
2343            &create_context(&command_runner, &Default::default(), &Default::default()),
2344            create_pool_config(
2345                vec![pool_build("foo", limited_pool("bar"))],
2346                &["foo"],
2347                &[("bar", usize::MAX)],
2348            ),
2349            &[],
2350            DEFAULT_OPTIONS,
2351        )
2352        .await
2353        .unwrap();
2354
2355        assert_eq!(command_runner.commands(), ["touch foo"]);
2356    }
2357
2358    #[tokio::test]
2359    async fn run_build_in_console_pool() {
2360        let command_runner = FakeCommandRunner::new(
2361            [(
2362                "touch foo".into(),
2363                Output {
2364                    status: ExitStatus::default(),
2365                    stdout: b"bar\n".into(),
2366                    stderr: b"baz\n".into(),
2367                },
2368            )]
2369            .into_iter()
2370            .collect(),
2371        );
2372        let console = FakeConsole::default();
2373
2374        run(
2375            &create_context(&command_runner, &console, &Default::default()),
2376            create_simple_config(
2377                vec![explicit_build(
2378                    vec!["foo".into()],
2379                    Rule::new("touch foo", Some("build foo".into())).with_pool(Some(Pool::Console)),
2380                    vec![],
2381                )],
2382                &["foo"],
2383            ),
2384            &[],
2385            DEFAULT_OPTIONS,
2386        )
2387        .await
2388        .unwrap();
2389
2390        assert_eq!(command_runner.commands(), Vec::<String>::new());
2391        assert_eq!(command_runner.console_commands(), ["touch foo"]);
2392        assert_eq!(console.stdout(), "");
2393        assert_eq!(console.stderr(), "build foo\n");
2394    }
2395
2396    #[tokio::test]
2397    async fn limit_concurrency_of_builds_in_console_pool() {
2398        let command_runner = FakeCommandRunner::default();
2399
2400        run(
2401            &create_context(&command_runner, &Default::default(), &Default::default()),
2402            create_simple_config(
2403                vec![
2404                    pool_build("foo", Some(Pool::Console)),
2405                    pool_build("bar", Some(Pool::Console)),
2406                    pool_build("baz", Some(Pool::Console)),
2407                ],
2408                &["foo", "bar", "baz"],
2409            ),
2410            &[],
2411            DEFAULT_OPTIONS,
2412        )
2413        .await
2414        .unwrap();
2415
2416        assert_eq!(command_runner.console_commands().len(), 3);
2417        assert_eq!(command_runner.max_concurrency(), 1);
2418    }
2419
2420    #[tokio::test]
2421    async fn write_debug_log_of_failed_build_in_console_pool() {
2422        let command_runner =
2423            FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
2424        let console = FakeConsole::default();
2425
2426        assert_eq!(
2427            run(
2428                &create_context(&command_runner, &console, &Default::default()),
2429                create_simple_config(
2430                    vec![explicit_build(
2431                        vec!["foo".into()],
2432                        Rule::new("exit 1", None).with_pool(Some(Pool::Console)),
2433                        vec![],
2434                    )],
2435                    &["foo"],
2436                ),
2437                &[],
2438                RunOptions {
2439                    debug: true,
2440                    profile: false,
2441                },
2442            )
2443            .await,
2444            Err(BuildError::Build)
2445        );
2446        assert_eq!(command_runner.console_commands(), ["exit 1"]);
2447        assert_eq!(
2448            console.stderr(),
2449            "turtle: command: exit 1\nturtle: exit status: 1\n"
2450        );
2451    }
2452
2453    #[tokio::test]
2454    async fn lock_console_during_command_in_console_pool() {
2455        let command_runner = FakeCommandRunner::default();
2456        let context = create_run_context(&command_runner, &Default::default(), &[]);
2457        let rule = Rule::new("foo", None).with_pool(Some(Pool::Console));
2458        let mut future = pin!(run_rule(&context, &rule));
2459
2460        assert!(poll!(&mut future).is_pending());
2461        assert_eq!(command_runner.console_commands(), ["foo"]);
2462        assert!(context.build().console().try_lock().is_err());
2463
2464        future.await.unwrap();
2465
2466        assert!(context.build().console().try_lock().is_ok());
2467    }
2468
2469    #[tokio::test]
2470    async fn flush_console_before_command_in_console_pool() {
2471        let command_runner = FakeCommandRunner::default();
2472        let console = FakeConsole::default();
2473        let context = create_run_context(&command_runner, &console, &[]);
2474        let rule = Rule::new("foo", Some("bar".into())).with_pool(Some(Pool::Console));
2475        let mut future = pin!(run_rule(&context, &rule));
2476
2477        assert!(poll!(&mut future).is_pending());
2478        assert_eq!(command_runner.console_commands(), ["foo"]);
2479        assert_eq!(console.flushed_stderr(), "bar\n");
2480
2481        future.await.unwrap();
2482    }
2483
2484    #[tokio::test]
2485    async fn wait_for_console_before_command_in_console_pool() {
2486        let command_runner = FakeCommandRunner::default();
2487        let context = create_run_context(&command_runner, &Default::default(), &[]);
2488        let rule = Rule::new("foo", None).with_pool(Some(Pool::Console));
2489        let console = context.build().console().lock().await;
2490        let mut future = pin!(run_rule(&context, &rule));
2491
2492        for _ in 0..POLL_COUNT {
2493            assert!(poll!(&mut future).is_pending());
2494        }
2495
2496        assert_eq!(command_runner.console_commands(), Vec::<String>::new());
2497
2498        drop(console);
2499        future.await.unwrap();
2500
2501        assert_eq!(command_runner.console_commands(), ["foo"]);
2502    }
2503
2504    #[tokio::test]
2505    async fn run_command_concurrently_with_command_in_console_pool() {
2506        let command_runner = FakeCommandRunner::default();
2507        let context = create_run_context(&command_runner, &Default::default(), &[]);
2508        let foo = Rule::new("foo", None).with_pool(Some(Pool::Console));
2509        let bar = Rule::new("bar", None);
2510        let mut foo_future = pin!(run_rule(&context, &foo));
2511        let mut bar_future = pin!(run_rule(&context, &bar));
2512
2513        assert!(poll!(&mut foo_future).is_pending());
2514        assert!(poll!(&mut bar_future).is_pending());
2515        assert_eq!(command_runner.console_commands(), ["foo"]);
2516        assert_eq!(command_runner.commands(), ["bar"]);
2517
2518        foo_future.await.unwrap();
2519        bar_future.await.unwrap();
2520    }
2521
2522    #[tokio::test]
2523    async fn release_pool_before_waiting_for_console() {
2524        let command_runner = FakeCommandRunner::default();
2525        let context = create_run_context(&command_runner, &Default::default(), &[("baz", 1)]);
2526        let foo = Rule::new("foo", None).with_pool(limited_pool("baz"));
2527        let bar = Rule::new("bar", None).with_pool(limited_pool("baz"));
2528        let console = context.build().console().lock().await;
2529        let mut foo_future = pin!(run_rule(&context, &foo));
2530        let mut bar_future = pin!(run_rule(&context, &bar));
2531
2532        for _ in 0..POLL_COUNT {
2533            assert!(poll!(&mut foo_future).is_pending());
2534        }
2535
2536        for _ in 0..POLL_COUNT {
2537            assert!(poll!(&mut bar_future).is_pending());
2538        }
2539
2540        assert_eq!(command_runner.commands(), ["foo", "bar"]);
2541
2542        drop(console);
2543        foo_future.await.unwrap();
2544        bar_future.await.unwrap();
2545    }
2546
2547    #[tokio::test]
2548    async fn wait_for_pool_before_locking_console() {
2549        let command_runner = FakeCommandRunner::default();
2550        let console = FakeConsole::default();
2551        let context = create_run_context(&command_runner, &console, &[("bar", 1)]);
2552        let rule = Rule::new("foo", Some("foo".into())).with_pool(limited_pool("bar"));
2553        let permit = context.pool(rule.pool()).await.unwrap();
2554        let mut future = pin!(run_rule(&context, &rule));
2555
2556        for _ in 0..POLL_COUNT {
2557            assert!(poll!(&mut future).is_pending());
2558        }
2559
2560        assert_eq!(command_runner.commands(), Vec::<String>::new());
2561        assert_eq!(console.stderr(), "");
2562        assert!(context.build().console().try_lock().is_ok());
2563
2564        drop(permit);
2565        future.await.unwrap();
2566
2567        assert_eq!(command_runner.commands(), ["foo"]);
2568        assert_eq!(console.stderr(), "foo\n");
2569    }
2570
2571    #[tokio::test]
2572    async fn keep_pool_until_command_finishes_on_console_error() {
2573        let command_runner = FakeCommandRunner::default();
2574        let context = create_run_context(&command_runner, &FakeConsole::failing(), &[("baz", 1)]);
2575        let foo = Rule::new("foo", Some("foo".into())).with_pool(limited_pool("baz"));
2576        let bar = Rule::new("bar", None).with_pool(limited_pool("baz"));
2577        let mut foo_future = pin!(run_rule(&context, &foo));
2578        let mut bar_future = pin!(run_rule(&context, &bar));
2579
2580        assert!(poll!(&mut foo_future).is_pending());
2581        assert!(poll!(&mut bar_future).is_pending());
2582        assert_eq!(command_runner.commands(), ["foo"]);
2583        assert!(foo_future.await.is_err());
2584    }
2585
2586    #[test]
2587    fn classify_phony_and_file_inputs() {
2588        let build = explicit_build(
2589            vec!["foo".into()],
2590            Rule::new("", None),
2591            vec!["bar".into(), "baz".into(), "qux".into(), "bar".into()],
2592        );
2593        let context = RunContext::new(
2594            create_context(
2595                &Default::default(),
2596                &Default::default(),
2597                &Default::default(),
2598            ),
2599            create_simple_config(
2600                vec![
2601                    build.clone(),
2602                    Build::new(vec!["bar".into()], vec![], None, vec![], vec![], None),
2603                    explicit_build(vec!["baz".into()], Rule::new("", None), vec![]),
2604                ],
2605                &[],
2606            ),
2607            BuildGraph::new(&Default::default()),
2608            Default::default(),
2609            DEFAULT_OPTIONS,
2610        );
2611
2612        assert_eq!(
2613            classify_inputs(
2614                &context,
2615                &build,
2616                &["quux".into(), "baz".into()],
2617                &["qux", "corge"],
2618            ),
2619            (vec!["bar"], vec!["baz", "qux", "quux", "corge"])
2620        );
2621    }
2622}