Merge "Upgrade num_cpus to 1.14.0" am: 5dc4c41b88 am: d475020063

Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/num_cpus/+/2344988

Change-Id: I24241629c85dd108c179ce0ea96c5863ec2ead5f
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index 45e1a87..663a551 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,6 @@
 {
   "git": {
-    "sha1": "5f1b03332000b4c4274b5bd35fac516049ff1c6b"
+    "sha1": "90373f3865f92a0e8c5e137738fb91df19bea7e0"
   },
   "path_in_vcs": ""
 }
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
index 5cc8a47..37c6a88 100644
--- a/Android.bp
+++ b/Android.bp
@@ -42,7 +42,7 @@
     host_supported: true,
     crate_name: "num_cpus",
     cargo_env_compat: true,
-    cargo_pkg_version: "1.13.1",
+    cargo_pkg_version: "1.14.0",
     srcs: ["src/lib.rs"],
     edition: "2015",
     rustlibs: [
@@ -64,7 +64,7 @@
     name: "num_cpus_test_src_lib",
     crate_name: "num_cpus",
     cargo_env_compat: true,
-    cargo_pkg_version: "1.13.1",
+    cargo_pkg_version: "1.14.0",
     srcs: ["src/lib.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5496ace..6680c79 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## v1.14.0
+
+### Features
+
+- add support for cgroups v2
+- Skip reading files in Miri
+
 ## v1.13.1
 
 ### Fixes
diff --git a/Cargo.lock b/Cargo.lock.saved
similarity index 96%
rename from Cargo.lock
rename to Cargo.lock.saved
index 93137c3..30bfe74 100644
--- a/Cargo.lock
+++ b/Cargo.lock.saved
@@ -19,7 +19,7 @@
 
 [[package]]
 name = "num_cpus"
-version = "1.13.1"
+version = "1.14.0"
 dependencies = [
  "hermit-abi",
  "libc",
diff --git a/Cargo.toml b/Cargo.toml
index f708cc8..97b3a78 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,16 +11,22 @@
 
 [package]
 name = "num_cpus"
-version = "1.13.1"
+version = "1.14.0"
 authors = ["Sean McArthur <sean@seanmonstar.com>"]
 description = "Get the number of CPUs on a machine."
 documentation = "https://docs.rs/num_cpus"
 readme = "README.md"
-keywords = ["cpu", "cpus", "cores"]
+keywords = [
+    "cpu",
+    "cpus",
+    "cores",
+]
 categories = ["hardware-support"]
 license = "MIT OR Apache-2.0"
 repository = "https://github.com/seanmonstar/num_cpus"
+
 [target."cfg(all(any(target_arch = \"x86_64\", target_arch = \"aarch64\"), target_os = \"hermit\"))".dependencies.hermit-abi]
 version = "0.1.3"
+
 [target."cfg(not(windows))".dependencies.libc]
 version = "0.2.26"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index a6cbbd2..d3e02d4 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,7 +1,7 @@
 [package]
 
 name = "num_cpus"
-version = "1.13.1"
+version = "1.14.0"
 description = "Get the number of CPUs on a machine."
 authors = ["Sean McArthur <sean@seanmonstar.com>"]
 license = "MIT OR Apache-2.0"
diff --git a/METADATA b/METADATA
index f0d949f..6459f56 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,7 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update rust/crates/num_cpus
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+
 name: "num_cpus"
 description: "Get the number of CPUs on a machine."
 third_party {
@@ -7,13 +11,13 @@
   }
   url {
     type: ARCHIVE
-    value: "https://static.crates.io/crates/num_cpus/num_cpus-1.13.1.crate"
+    value: "https://static.crates.io/crates/num_cpus/num_cpus-1.14.0.crate"
   }
-  version: "1.13.1"
+  version: "1.14.0"
   license_type: NOTICE
   last_upgrade_date {
     year: 2022
-    month: 3
-    day: 1
+    month: 12
+    day: 13
   }
 }
diff --git a/fixtures/cgroups2/cgroups/ceil/cpu.max b/fixtures/cgroups2/cgroups/ceil/cpu.max
new file mode 100644
index 0000000..833a8f2
--- /dev/null
+++ b/fixtures/cgroups2/cgroups/ceil/cpu.max
@@ -0,0 +1 @@
+150000 100000
diff --git a/fixtures/cgroups2/cgroups/good/cpu.max b/fixtures/cgroups2/cgroups/good/cpu.max
new file mode 100644
index 0000000..e469067
--- /dev/null
+++ b/fixtures/cgroups2/cgroups/good/cpu.max
@@ -0,0 +1 @@
+600000 100000
diff --git a/fixtures/cgroups2/cgroups/zero-period/cpu.max b/fixtures/cgroups2/cgroups/zero-period/cpu.max
new file mode 100644
index 0000000..24e757f
--- /dev/null
+++ b/fixtures/cgroups2/cgroups/zero-period/cpu.max
@@ -0,0 +1 @@
+600000 0
diff --git a/fixtures/cgroups2/proc/cgroups/cgroup b/fixtures/cgroups2/proc/cgroups/cgroup
new file mode 100644
index 0000000..35b49db
--- /dev/null
+++ b/fixtures/cgroups2/proc/cgroups/cgroup
@@ -0,0 +1,2 @@
+12::/
+3::/user.slice
diff --git a/fixtures/cgroups2/proc/cgroups/cgroup_multi b/fixtures/cgroups2/proc/cgroups/cgroup_multi
new file mode 100644
index 0000000..1a9282a
--- /dev/null
+++ b/fixtures/cgroups2/proc/cgroups/cgroup_multi
@@ -0,0 +1,3 @@
+12::/
+11:cpu,cpuacct:/
+3::/user.slice
diff --git a/fixtures/cgroups2/proc/cgroups/mountinfo b/fixtures/cgroups2/proc/cgroups/mountinfo
new file mode 100644
index 0000000..da36e41
--- /dev/null
+++ b/fixtures/cgroups2/proc/cgroups/mountinfo
@@ -0,0 +1,5 @@
+1 0 8:1 / / rw,noatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=reordered
+2 1 0:1 / /dev rw,relatime shared:2 - devtmpfs udev rw,size=10240k,nr_inodes=16487629,mode=755
+3 1 0:2 / /proc rw,nosuid,nodev,noexec,relatime shared:3 - proc proc rw
+4 1 0:3 / /sys rw,nosuid,nodev,noexec,relatime shared:4 - sysfs sysfs rw
+5 4 0:4 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:5 - cgroup2 cgroup2 rw,nsdelegate,memory_recursiveprot
diff --git a/src/linux.rs b/src/linux.rs
index 36f4727..295c925 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -126,6 +126,11 @@
     // Should only be called once
     debug_assert!(CGROUPS_CPUS.load(Ordering::SeqCst) == 0);
 
+    // Fails in Miri by default (cannot open files), and Miri does not have parallelism anyway.
+    if cfg!(miri) {
+        return;
+    }
+
     if let Some(quota) = load_cgroups("/proc/self/cgroup", "/proc/self/mountinfo") {
         if quota == 0 {
             return;
@@ -144,27 +149,36 @@
     P2: AsRef<Path>,
 {
     let subsys = some!(Subsys::load_cpu(cgroup_proc));
-    let mntinfo = some!(MountInfo::load_cpu(mountinfo_proc));
+    let mntinfo = some!(MountInfo::load_cpu(mountinfo_proc, subsys.version));
     let cgroup = some!(Cgroup::translate(mntinfo, subsys));
     cgroup.cpu_quota()
 }
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum CgroupVersion {
+    V1,
+    V2,
+}
+
 struct Cgroup {
+    version: CgroupVersion,
     base: PathBuf,
 }
 
 struct MountInfo {
+    version: CgroupVersion,
     root: String,
     mount_point: String,
 }
 
 struct Subsys {
+    version: CgroupVersion,
     base: String,
 }
 
 impl Cgroup {
-    fn new(dir: PathBuf) -> Cgroup {
-        Cgroup { base: dir }
+    fn new(version: CgroupVersion, dir: PathBuf) -> Cgroup {
+        Cgroup { version: version, base: dir }
     }
 
     fn translate(mntinfo: MountInfo, subsys: Subsys) -> Option<Cgroup> {
@@ -181,12 +195,14 @@
         // join(mp.MountPoint, relPath)
         let mut path = PathBuf::from(mntinfo.mount_point);
         path.push(rel_from_root);
-        Some(Cgroup::new(path))
+        Some(Cgroup::new(mntinfo.version, path))
     }
 
     fn cpu_quota(&self) -> Option<usize> {
-        let quota_us = some!(self.quota_us());
-        let period_us = some!(self.period_us());
+        let (quota_us, period_us) = match self.version {
+            CgroupVersion::V1 => (some!(self.quota_us()), some!(self.period_us())),
+            CgroupVersion::V2 => some!(self.max()),
+        };
 
         // protect against dividing by zero
         if period_us == 0 {
@@ -207,25 +223,41 @@
         self.param("cpu.cfs_period_us")
     }
 
+    fn max(&self) -> Option<(usize, usize)> {
+        let max = some!(self.raw_param("cpu.max"));
+        let mut max = some!(max.lines().next()).split(' ');
+
+        let quota = some!(max.next().and_then(|quota| quota.parse().ok()));
+        let period = some!(max.next().and_then(|period| period.parse().ok()));
+
+        Some((quota, period))
+    }
+
     fn param(&self, param: &str) -> Option<usize> {
+        let buf = some!(self.raw_param(param));
+
+        buf.trim().parse().ok()
+    }
+
+    fn raw_param(&self, param: &str) -> Option<String> {
         let mut file = some!(File::open(self.base.join(param)).ok());
 
         let mut buf = String::new();
         some!(file.read_to_string(&mut buf).ok());
 
-        buf.trim().parse().ok()
+        Some(buf)
     }
 }
 
 impl MountInfo {
-    fn load_cpu<P: AsRef<Path>>(proc_path: P) -> Option<MountInfo> {
+    fn load_cpu<P: AsRef<Path>>(proc_path: P, version: CgroupVersion) -> Option<MountInfo> {
         let file = some!(File::open(proc_path).ok());
         let file = BufReader::new(file);
 
         file.lines()
             .filter_map(|result| result.ok())
             .filter_map(MountInfo::parse_line)
-            .next()
+            .find(|mount_info| mount_info.version == version)
     }
 
     fn parse_line(line: String) -> Option<MountInfo> {
@@ -247,19 +279,25 @@
         };
 
         // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - <cgroup> cgroup rw,cpu,cpuacct
-        if fields.next() != Some("cgroup") {
-            return None;
-        }
+        let version = match fields.next() {
+            Some("cgroup") => CgroupVersion::V1,
+            Some("cgroup2") => CgroupVersion::V2,
+            _ => return None,
+        };
 
-        // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup <rw,cpu,cpuacct>
-        let super_opts = some!(fields.nth(1));
+        // cgroups2 only has a single mount point
+        if version == CgroupVersion::V1 {
+            // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup <rw,cpu,cpuacct>
+            let super_opts = some!(fields.nth(1));
 
-        // We only care about the 'cpu' option
-        if !super_opts.split(',').any(|opt| opt == "cpu") {
-            return None;
+            // We only care about the 'cpu' option
+            if !super_opts.split(',').any(|opt| opt == "cpu") {
+                return None;
+            }
         }
 
         Some(MountInfo {
+            version: version,
             root: mnt_root.to_owned(),
             mount_point: mnt_point.to_owned(),
         })
@@ -274,7 +312,14 @@
         file.lines()
             .filter_map(|result| result.ok())
             .filter_map(Subsys::parse_line)
-            .next()
+            .fold(None, |previous, line| {
+                // already-found v1 trumps v2 since it explicitly specifies its controllers
+                if previous.is_some() && line.version == CgroupVersion::V2 {
+                    return previous;
+                }
+
+                Some(line)
+            })
     }
 
     fn parse_line(line: String) -> Option<Subsys> {
@@ -284,11 +329,18 @@
 
         let sub_systems = some!(fields.nth(1));
 
-        if !sub_systems.split(',').any(|sub| sub == "cpu") {
+        let version = if sub_systems.is_empty() {
+            CgroupVersion::V2
+        } else {
+            CgroupVersion::V1
+        };
+
+        if version == CgroupVersion::V1 && !sub_systems.split(',').any(|sub| sub == "cpu") {
             return None;
         }
 
         fields.next().map(|path| Subsys {
+            version: version,
             base: path.to_owned(),
         })
     }
@@ -296,123 +348,248 @@
 
 #[cfg(test)]
 mod tests {
-    use super::{Cgroup, MountInfo, Subsys};
-    use std::path::{Path, PathBuf};
+    mod v1 {
+        use super::super::{Cgroup, CgroupVersion, MountInfo, Subsys};
+        use std::path::{Path, PathBuf};
 
-    // `static_in_const` feature is not stable in Rust 1.13.
-    static FIXTURES_PROC: &'static str = "fixtures/cgroups/proc/cgroups";
+        // `static_in_const` feature is not stable in Rust 1.13.
+        static FIXTURES_PROC: &'static str = "fixtures/cgroups/proc/cgroups";
 
-    static FIXTURES_CGROUPS: &'static str = "fixtures/cgroups/cgroups";
+        static FIXTURES_CGROUPS: &'static str = "fixtures/cgroups/cgroups";
 
-    macro_rules! join {
-        ($base:expr, $($path:expr),+) => ({
-            Path::new($base)
-                $(.join($path))+
-        })
-    }
+        macro_rules! join {
+            ($base:expr, $($path:expr),+) => ({
+                Path::new($base)
+                    $(.join($path))+
+            })
+        }
 
-    #[test]
-    fn test_load_mountinfo() {
-        // test only one optional fields
-        let path = join!(FIXTURES_PROC, "mountinfo");
+        #[test]
+        fn test_load_mountinfo() {
+            // test only one optional fields
+            let path = join!(FIXTURES_PROC, "mountinfo");
 
-        let mnt_info = MountInfo::load_cpu(path).unwrap();
+            let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V1).unwrap();
 
-        assert_eq!(mnt_info.root, "/");
-        assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct");
+            assert_eq!(mnt_info.root, "/");
+            assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct");
 
-        // test zero optional field
-        let path = join!(FIXTURES_PROC, "mountinfo_zero_opt");
+            // test zero optional field
+            let path = join!(FIXTURES_PROC, "mountinfo_zero_opt");
 
-        let mnt_info = MountInfo::load_cpu(path).unwrap();
+            let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V1).unwrap();
 
-        assert_eq!(mnt_info.root, "/");
-        assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct");
+            assert_eq!(mnt_info.root, "/");
+            assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct");
 
-        // test multi optional fields
-        let path = join!(FIXTURES_PROC, "mountinfo_multi_opt");
+            // test multi optional fields
+            let path = join!(FIXTURES_PROC, "mountinfo_multi_opt");
 
-        let mnt_info = MountInfo::load_cpu(path).unwrap();
+            let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V1).unwrap();
 
-        assert_eq!(mnt_info.root, "/");
-        assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct");
-    }
+            assert_eq!(mnt_info.root, "/");
+            assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct");
+        }
 
-    #[test]
-    fn test_load_subsys() {
-        let path = join!(FIXTURES_PROC, "cgroup");
+        #[test]
+        fn test_load_subsys() {
+            let path = join!(FIXTURES_PROC, "cgroup");
 
-        let subsys = Subsys::load_cpu(path).unwrap();
+            let subsys = Subsys::load_cpu(path).unwrap();
 
-        assert_eq!(subsys.base, "/");
-    }
+            assert_eq!(subsys.base, "/");
+            assert_eq!(subsys.version, CgroupVersion::V1);
+        }
 
-    #[test]
-    fn test_cgroup_mount() {
-        let cases = &[
-            ("/", "/sys/fs/cgroup/cpu", "/", Some("/sys/fs/cgroup/cpu")),
-            (
-                "/docker/01abcd",
-                "/sys/fs/cgroup/cpu",
-                "/docker/01abcd",
-                Some("/sys/fs/cgroup/cpu"),
-            ),
-            (
-                "/docker/01abcd",
-                "/sys/fs/cgroup/cpu",
-                "/docker/01abcd/",
-                Some("/sys/fs/cgroup/cpu"),
-            ),
-            (
-                "/docker/01abcd",
-                "/sys/fs/cgroup/cpu",
-                "/docker/01abcd/large",
-                Some("/sys/fs/cgroup/cpu/large"),
-            ),
-            // fails
-            ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/", None),
-            ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/docker", None),
-            ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/elsewhere", None),
-            (
-                "/docker/01abcd",
-                "/sys/fs/cgroup/cpu",
-                "/docker/01abcd-other-dir",
-                None,
-            ),
-        ];
+        #[test]
+        fn test_cgroup_mount() {
+            let cases = &[
+                ("/", "/sys/fs/cgroup/cpu", "/", Some("/sys/fs/cgroup/cpu")),
+                (
+                    "/docker/01abcd",
+                    "/sys/fs/cgroup/cpu",
+                    "/docker/01abcd",
+                    Some("/sys/fs/cgroup/cpu"),
+                ),
+                (
+                    "/docker/01abcd",
+                    "/sys/fs/cgroup/cpu",
+                    "/docker/01abcd/",
+                    Some("/sys/fs/cgroup/cpu"),
+                ),
+                (
+                    "/docker/01abcd",
+                    "/sys/fs/cgroup/cpu",
+                    "/docker/01abcd/large",
+                    Some("/sys/fs/cgroup/cpu/large"),
+                ),
+                // fails
+                ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/", None),
+                ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/docker", None),
+                ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/elsewhere", None),
+                (
+                    "/docker/01abcd",
+                    "/sys/fs/cgroup/cpu",
+                    "/docker/01abcd-other-dir",
+                    None,
+                ),
+            ];
 
-        for &(root, mount_point, subsys, expected) in cases.iter() {
-            let mnt_info = MountInfo {
-                root: root.into(),
-                mount_point: mount_point.into(),
-            };
-            let subsys = Subsys {
-                base: subsys.into(),
-            };
+            for &(root, mount_point, subsys, expected) in cases.iter() {
+                let mnt_info = MountInfo {
+                    version: CgroupVersion::V1,
+                    root: root.into(),
+                    mount_point: mount_point.into(),
+                };
+                let subsys = Subsys {
+                    version: CgroupVersion::V1,
+                    base: subsys.into(),
+                };
 
-            let actual = Cgroup::translate(mnt_info, subsys).map(|c| c.base);
-            let expected = expected.map(PathBuf::from);
-            assert_eq!(actual, expected);
+                let actual = Cgroup::translate(mnt_info, subsys).map(|c| c.base);
+                let expected = expected.map(PathBuf::from);
+                assert_eq!(actual, expected);
+            }
+        }
+
+        #[test]
+        fn test_cgroup_cpu_quota() {
+            let cgroup = Cgroup::new(CgroupVersion::V1, join!(FIXTURES_CGROUPS, "good"));
+            assert_eq!(cgroup.cpu_quota(), Some(6));
+        }
+
+        #[test]
+        fn test_cgroup_cpu_quota_divide_by_zero() {
+            let cgroup = Cgroup::new(CgroupVersion::V1, join!(FIXTURES_CGROUPS, "zero-period"));
+            assert!(cgroup.quota_us().is_some());
+            assert_eq!(cgroup.period_us(), Some(0));
+            assert_eq!(cgroup.cpu_quota(), None);
+        }
+
+        #[test]
+        fn test_cgroup_cpu_quota_ceil() {
+            let cgroup = Cgroup::new(CgroupVersion::V1, join!(FIXTURES_CGROUPS, "ceil"));
+            assert_eq!(cgroup.cpu_quota(), Some(2));
         }
     }
 
-    #[test]
-    fn test_cgroup_cpu_quota() {
-        let cgroup = Cgroup::new(join!(FIXTURES_CGROUPS, "good"));
-        assert_eq!(cgroup.cpu_quota(), Some(6));
-    }
+    mod v2 {
+        use super::super::{Cgroup, CgroupVersion, MountInfo, Subsys};
+        use std::path::{Path, PathBuf};
 
-    #[test]
-    fn test_cgroup_cpu_quota_divide_by_zero() {
-        let cgroup = Cgroup::new(join!(FIXTURES_CGROUPS, "zero-period"));
-        assert!(cgroup.quota_us().is_some());
-        assert_eq!(cgroup.period_us(), Some(0));
-        assert_eq!(cgroup.cpu_quota(), None);
-    }
+        // `static_in_const` feature is not stable in Rust 1.13.
+        static FIXTURES_PROC: &'static str = "fixtures/cgroups2/proc/cgroups";
 
-    #[test]
-    fn test_cgroup_cpu_quota_ceil() {
-        let cgroup = Cgroup::new(join!(FIXTURES_CGROUPS, "ceil"));
-        assert_eq!(cgroup.cpu_quota(), Some(2));
+        static FIXTURES_CGROUPS: &'static str = "fixtures/cgroups2/cgroups";
+
+        macro_rules! join {
+            ($base:expr, $($path:expr),+) => ({
+                Path::new($base)
+                    $(.join($path))+
+            })
+        }
+
+        #[test]
+        fn test_load_mountinfo() {
+            // test only one optional fields
+            let path = join!(FIXTURES_PROC, "mountinfo");
+
+            let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V2).unwrap();
+
+            assert_eq!(mnt_info.root, "/");
+            assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup");
+        }
+
+        #[test]
+        fn test_load_subsys() {
+            let path = join!(FIXTURES_PROC, "cgroup");
+
+            let subsys = Subsys::load_cpu(path).unwrap();
+
+            assert_eq!(subsys.base, "/");
+            assert_eq!(subsys.version, CgroupVersion::V2);
+        }
+
+        #[test]
+        fn test_load_subsys_multi() {
+            let path = join!(FIXTURES_PROC, "cgroup_multi");
+
+            let subsys = Subsys::load_cpu(path).unwrap();
+
+            assert_eq!(subsys.base, "/");
+            assert_eq!(subsys.version, CgroupVersion::V1);
+        }
+
+        #[test]
+        fn test_cgroup_mount() {
+            let cases = &[
+                ("/", "/sys/fs/cgroup/cpu", "/", Some("/sys/fs/cgroup/cpu")),
+                (
+                    "/docker/01abcd",
+                    "/sys/fs/cgroup/cpu",
+                    "/docker/01abcd",
+                    Some("/sys/fs/cgroup/cpu"),
+                ),
+                (
+                    "/docker/01abcd",
+                    "/sys/fs/cgroup/cpu",
+                    "/docker/01abcd/",
+                    Some("/sys/fs/cgroup/cpu"),
+                ),
+                (
+                    "/docker/01abcd",
+                    "/sys/fs/cgroup/cpu",
+                    "/docker/01abcd/large",
+                    Some("/sys/fs/cgroup/cpu/large"),
+                ),
+                // fails
+                ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/", None),
+                ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/docker", None),
+                ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/elsewhere", None),
+                (
+                    "/docker/01abcd",
+                    "/sys/fs/cgroup/cpu",
+                    "/docker/01abcd-other-dir",
+                    None,
+                ),
+            ];
+
+            for &(root, mount_point, subsys, expected) in cases.iter() {
+                let mnt_info = MountInfo {
+                    version: CgroupVersion::V1,
+                    root: root.into(),
+                    mount_point: mount_point.into(),
+                };
+                let subsys = Subsys {
+                    version: CgroupVersion::V1,
+                    base: subsys.into(),
+                };
+
+                let actual = Cgroup::translate(mnt_info, subsys).map(|c| c.base);
+                let expected = expected.map(PathBuf::from);
+                assert_eq!(actual, expected);
+            }
+        }
+
+        #[test]
+        fn test_cgroup_cpu_quota() {
+            let cgroup = Cgroup::new(CgroupVersion::V2, join!(FIXTURES_CGROUPS, "good"));
+            assert_eq!(cgroup.cpu_quota(), Some(6));
+        }
+
+        #[test]
+        fn test_cgroup_cpu_quota_divide_by_zero() {
+            let cgroup = Cgroup::new(CgroupVersion::V2, join!(FIXTURES_CGROUPS, "zero-period"));
+            let period = cgroup.max().map(|max| max.1);
+
+            assert_eq!(period, Some(0));
+            assert_eq!(cgroup.cpu_quota(), None);
+        }
+
+        #[test]
+        fn test_cgroup_cpu_quota_ceil() {
+            let cgroup = Cgroup::new(CgroupVersion::V2, join!(FIXTURES_CGROUPS, "ceil"));
+            assert_eq!(cgroup.cpu_quota(), Some(2));
+        }
     }
 }