Skip to main content

turtle_build/
run.rs

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