Skip to main content

turtle_build/
run.rs

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