Skip to main content

turtle_build/
run.rs

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