Add `Fake` target type for testing.

Change-Id: I78023b6d489ba6a088b6367a442e7cca934d7e0d
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/qg/+/126001
Reviewed-by: Alexei Frolov <frolv@google.com>
Pigweed-Auto-Submit: Erik Gilling <konkers@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
diff --git a/qg/src/project/manifest.rs b/qg/src/project/manifest.rs
index bc57b14..590144f 100644
--- a/qg/src/project/manifest.rs
+++ b/qg/src/project/manifest.rs
@@ -105,6 +105,8 @@
 pub enum TargetType {
     Cipd(CipdPackage),
     Download(DownloadablePackage),
+    #[cfg(test)]
+    Fake(FakeTarget),
 }
 
 /// A downloadable CIPD package.
@@ -176,6 +178,14 @@
     pub url_parameters: HashMap<String, String>,
 }
 
+/// A fake target for testing.
+#[derive(Debug, Deserialize, Serialize)]
+#[cfg(test)]
+pub struct FakeTarget {
+    /// The number of unitless ticks this target takes to execute.
+    pub duration_ticks: u32,
+}
+
 impl Manifest {
     pub fn new(name: &str) -> Self {
         Self {
diff --git a/qg/src/project/mod.rs b/qg/src/project/mod.rs
index af14659..c4ff0d4 100644
--- a/qg/src/project/mod.rs
+++ b/qg/src/project/mod.rs
@@ -273,6 +273,8 @@
 mod tests {
     use std::collections::HashSet;
 
+    use crate::target::Metadata;
+
     use super::*;
 
     #[test]
@@ -361,6 +363,7 @@
     fn simple_dependencies() {
         let project = Project::load("./src/test_projects/dependency_test").unwrap();
         assert_eq!(project.name, "dep-test");
+
         let registry = project.registry().unwrap();
         let a_id = registry.get_node_id("dep-test:a").unwrap();
         let b_id = registry.get_node_id("dep-test:b").unwrap();
@@ -368,19 +371,31 @@
         let d_id = registry.get_node_id("dep-test:d").unwrap();
         let e_id = registry.get_node_id("dep-test:e").unwrap();
 
-        let a_deps: HashSet<_> = registry.node_deps(a_id).collect();
-        assert_eq!(a_deps, [b_id, c_id].into_iter().collect());
+        for (name, id, duration_ticks, children) in [
+            ("a", a_id, 1, vec![b_id, c_id]),
+            ("b", b_id, 2, vec![]),
+            ("c", c_id, 3, vec![d_id, e_id]),
+            ("d", d_id, 4, vec![]),
+            ("e", e_id, 5, vec![]),
+        ] {
+            let target = registry.get_target_by_id(id).unwrap();
+            let Metadata::Fake(fake) = target.metadata() else {
+                panic!("{name} is not of the target type Fake: {target:?}.");
+            };
 
-        let b_deps: HashSet<_> = registry.node_deps(b_id).collect();
-        assert_eq!(b_deps, [].into_iter().collect());
+            assert_eq!(
+                fake.duration_ticks,
+                duration_ticks,
+                "Node {name}'s durations ticks ({}) do not match expected value ({duration_ticks}).",
+                fake.duration_ticks);
 
-        let c_deps: HashSet<_> = registry.node_deps(c_id).collect();
-        assert_eq!(c_deps, [d_id, e_id].into_iter().collect());
+            let deps: HashSet<_> = registry.node_deps(id).collect();
+            let expected: HashSet<_> = children.into_iter().collect();
 
-        let d_deps: HashSet<_> = registry.node_deps(d_id).collect();
-        assert_eq!(d_deps, [].into_iter().collect());
-
-        let e_deps: HashSet<_> = registry.node_deps(e_id).collect();
-        assert_eq!(e_deps, [].into_iter().collect());
+            assert_eq!(
+                deps, expected,
+                "Node {name}'s dependencies ({deps:?}) do not match expected value ({expected:?})."
+            );
+        }
     }
 }
diff --git a/qg/src/target.rs b/qg/src/target.rs
index ccf4d4a..c0c244d 100644
--- a/qg/src/target.rs
+++ b/qg/src/target.rs
@@ -72,6 +72,9 @@
     DepOnly,
     Cipd(Cipd),
     Download(Download),
+
+    #[cfg(test)]
+    Fake(Fake),
 }
 
 #[derive(Debug)]
@@ -199,6 +202,8 @@
         match value {
             manifest::TargetType::Cipd(data) => Ok(Self::Cipd(data.into())),
             manifest::TargetType::Download(data) => data.try_into().map(Self::Download),
+            #[cfg(test)]
+            manifest::TargetType::Fake(data) => Ok(Self::Fake(data.into())),
         }
     }
 }
@@ -263,6 +268,9 @@
             Metadata::DepOnly => "dep_only",
             Metadata::Cipd(_) => "cipd",
             Metadata::Download(_) => "download",
+
+            #[cfg(test)]
+            Metadata::Fake(_) => "fake",
         }
     }
 
@@ -273,6 +281,21 @@
     }
 }
 
+#[derive(Debug)]
+#[cfg(test)]
+pub struct Fake {
+    pub duration_ticks: u32,
+}
+
+#[cfg(test)]
+impl From<manifest::FakeTarget> for Fake {
+    fn from(value: manifest::FakeTarget) -> Self {
+        Self {
+            duration_ticks: value.duration_ticks,
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -413,4 +436,30 @@
         let err = Target::from_manifest("qg", "qg-provider", true, manifest_target).unwrap_err();
         assert!(matches!(err, Error::GenericErrorPlaceholder));
     }
+
+    #[test]
+    fn target_from_fake() {
+        let manifest_target = manifest::Target {
+            namespace: manifest::ProviderNamespace::Local,
+            deps: vec!["a".into(), "b".into()],
+            desc: Some(manifest::TargetType::Fake(manifest::FakeTarget {
+                duration_ticks: 42,
+            })),
+        };
+
+        let target =
+            Target::from_manifest("qg-fake", "qg-fake-provider", true, manifest_target).unwrap();
+        assert_eq!(target.name(), "qg-fake");
+        assert_eq!(target.provider(), "qg-fake-provider");
+        assert_eq!(target.full_name(), "qg-fake");
+        assert!(target.is_global());
+        assert_eq!(target.dependencies().count(), 2);
+        assert_eq!(target.type_string(), "fake");
+
+        let Metadata::Fake(fake) = target.metadata else {
+            panic!("target metadata is not of the Fake type.");
+        };
+
+        assert_eq!(fake.duration_ticks, 42);
+    }
 }
diff --git a/qg/src/test_projects/dependency_test/qg.toml b/qg/src/test_projects/dependency_test/qg.toml
index b5f2d0c..3c05bfb 100644
--- a/qg/src/test_projects/dependency_test/qg.toml
+++ b/qg/src/test_projects/dependency_test/qg.toml
@@ -8,18 +8,23 @@
 #     d  e
 
 [targets.a]
-type = "dep_only"
+type = "fake"
+duration_ticks = 1
 deps = ["dep-test:b", "dep-test:c"]
 
 [targets.b]
-type = "dep_only"
+type = "fake"
+duration_ticks = 2
 
 [targets.c]
-type = "dep_only"
+type = "fake"
+duration_ticks = 3
 deps = ["dep-test:d", "dep-test:e"]
 
 [targets.d]
-type = "dep_only"
+type = "fake"
+duration_ticks = 4
 
 [targets.e]
-type = "dep_only"
+type = "fake"
+duration_ticks = 5