From 1cc1337f0a24e281bd278d100349d273ea0bcf0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E7=8E=AE=E6=96=87?= Date: Fri, 21 Nov 2025 23:26:50 +0800 Subject: [PATCH] internal/runtime/cgroup: allow more tests to run on all OSes Move non-Linux specific part out of _linux.go. The parsing code itself doesn't depend anything Linux specific, even if it the format is Linux specific. This should benefit developers working on non-Linux OSes. Change-Id: I1692978d583c3dd9a57ff269c97e8fca53a7a057 Reviewed-on: https://go-review.googlesource.com/c/go/+/723240 Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt Reviewed-by: Carrillo Rodriguez --- src/internal/runtime/cgroup/cgroup.go | 418 ++++++++++++++++++ src/internal/runtime/cgroup/cgroup_linux.go | 410 ----------------- .../{cgroup_linux_test.go => cgroup_test.go} | 0 .../runtime/cgroup/export_linux_test.go | 15 - src/internal/runtime/cgroup/export_test.go | 10 + 5 files changed, 428 insertions(+), 425 deletions(-) create mode 100644 src/internal/runtime/cgroup/cgroup.go rename src/internal/runtime/cgroup/{cgroup_linux_test.go => cgroup_test.go} (100%) delete mode 100644 src/internal/runtime/cgroup/export_linux_test.go diff --git a/src/internal/runtime/cgroup/cgroup.go b/src/internal/runtime/cgroup/cgroup.go new file mode 100644 index 00000000000..68c31fcbc3b --- /dev/null +++ b/src/internal/runtime/cgroup/cgroup.go @@ -0,0 +1,418 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cgroup + +import ( + "internal/bytealg" + "internal/strconv" +) + +var ( + ErrNoCgroup error = stringError("not in a cgroup") + + errMalformedFile error = stringError("malformed file") +) + +const _PATH_MAX = 4096 + +const ( + // Required amount of scratch space for CPULimit. + // + // TODO(prattmic): This is shockingly large (~70KiB) due to the (very + // unlikely) combination of extremely long paths consisting mostly + // escaped characters. The scratch buffer ends up in .bss in package + // runtime, so it doesn't contribute to binary size and generally won't + // be faulted in, but it would still be nice to shrink this. A more + // complex parser that did not need to keep entire lines in memory + // could get away with much less. Alternatively, we could do a one-off + // mmap allocation for this buffer, which is only mapped larger if we + // actually need the extra space. + ScratchSize = PathSize + ParseSize + + // Required space to store a path of the cgroup in the filesystem. + PathSize = _PATH_MAX + + // /proc/self/mountinfo path escape sequences are 4 characters long, so + // a path consisting entirely of escaped characters could be 4 times + // larger. + escapedPathMax = 4 * _PATH_MAX + + // Required space to parse /proc/self/mountinfo and /proc/self/cgroup. + // See findCPUMount and findCPURelativePath. + ParseSize = 4 * escapedPathMax +) + +// Version indicates the cgroup version. +type Version int + +const ( + VersionUnknown Version = iota + V1 + V2 +) + +func parseV1Number(buf []byte) (int64, error) { + // Ignore trailing newline. + i := bytealg.IndexByte(buf, '\n') + if i < 0 { + return 0, errMalformedFile + } + buf = buf[:i] + + val, err := strconv.ParseInt(string(buf), 10, 64) + if err != nil { + return 0, errMalformedFile + } + + return val, nil +} + +func parseV2Limit(buf []byte) (float64, bool, error) { + i := bytealg.IndexByte(buf, ' ') + if i < 0 { + return 0, false, errMalformedFile + } + + quotaStr := buf[:i] + if bytealg.Compare(quotaStr, []byte("max")) == 0 { + // No limit. + return 0, false, nil + } + + periodStr := buf[i+1:] + // Ignore trailing newline, if any. + i = bytealg.IndexByte(periodStr, '\n') + if i < 0 { + return 0, false, errMalformedFile + } + periodStr = periodStr[:i] + + quota, err := strconv.ParseInt(string(quotaStr), 10, 64) + if err != nil { + return 0, false, errMalformedFile + } + + period, err := strconv.ParseInt(string(periodStr), 10, 64) + if err != nil { + return 0, false, errMalformedFile + } + + return float64(quota) / float64(period), true, nil +} + +// Finds the path of the current process's CPU cgroup relative to the cgroup +// mount and writes it to out. +// +// Returns the number of bytes written and the cgroup version (1 or 2). +func parseCPURelativePath(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, Version, error) { + // The format of each line is + // + // hierarchy-ID:controller-list:cgroup-path + // + // controller-list is comma-separated. + // See man 5 cgroup for more details. + // + // cgroup v2 has hierarchy-ID 0. If a v1 hierarchy contains "cpu", that + // is the CPU controller. Otherwise the v2 hierarchy (if any) is the + // CPU controller. + // + // hierarchy-ID and controller-list have relatively small maximum + // sizes, and the path can be up to _PATH_MAX, so we need a bit more + // than 1 _PATH_MAX of scratch space. + + l := newLineReader(fd, scratch, read) + + // Bytes written to out. + n := 0 + + for { + err := l.next() + if err == errIncompleteLine { + // Don't allow incomplete lines. While in theory the + // incomplete line may be for a controller we don't + // care about, in practice all lines should be of + // similar length, so we should just have a buffer big + // enough for any. + return 0, 0, err + } else if err == errEOF { + break + } else if err != nil { + return 0, 0, err + } + + line := l.line() + + // The format of each line is + // + // hierarchy-ID:controller-list:cgroup-path + // + // controller-list is comma-separated. + // See man 5 cgroup for more details. + i := bytealg.IndexByte(line, ':') + if i < 0 { + return 0, 0, errMalformedFile + } + + hierarchy := line[:i] + line = line[i+1:] + + i = bytealg.IndexByte(line, ':') + if i < 0 { + return 0, 0, errMalformedFile + } + + controllers := line[:i] + line = line[i+1:] + + path := line + + if string(hierarchy) == "0" { + // v2 hierarchy. + n = copy(out, path) + // Keep searching, we might find a v1 hierarchy with a + // CPU controller, which takes precedence. + } else { + // v1 hierarchy + if containsCPU(controllers) { + // Found a v1 CPU controller. This must be the + // only one, so we're done. + return copy(out, path), V1, nil + } + } + } + + if n == 0 { + // Found nothing. + return 0, 0, ErrNoCgroup + } + + // Must be v2, v1 returns above. + return n, V2, nil +} + +// Returns true if comma-separated list b contains "cpu". +func containsCPU(b []byte) bool { + for len(b) > 0 { + i := bytealg.IndexByte(b, ',') + if i < 0 { + // Neither cmd/compile nor gccgo allocates for these string conversions. + return string(b) == "cpu" + } + + curr := b[:i] + rest := b[i+1:] + + if string(curr) == "cpu" { + return true + } + + b = rest + } + + return false +} + +// Returns the mount point for the cpu cgroup controller (v1 or v2) from +// /proc/self/mountinfo. +func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, error) { + // The format of each line is: + // + // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue + // (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) + // + // (1) mount ID: unique identifier of the mount (may be reused after umount) + // (2) parent ID: ID of parent (or of self for the top of the mount tree) + // (3) major:minor: value of st_dev for files on filesystem + // (4) root: root of the mount within the filesystem + // (5) mount point: mount point relative to the process's root + // (6) mount options: per mount options + // (7) optional fields: zero or more fields of the form "tag[:value]" + // (8) separator: marks the end of the optional fields + // (9) filesystem type: name of filesystem of the form "type[.subtype]" + // (10) mount source: filesystem specific information or "none" + // (11) super options: per super block options + // + // See man 5 proc_pid_mountinfo for more details. + // + // Note that emitted paths will not contain space, tab, newline, or + // carriage return. Those are escaped. See Linux show_mountinfo -> + // show_path. We must unescape before returning. + // + // We return the mount point (5) if the filesystem type (9) is cgroup2, + // or cgroup with "cpu" in the super options (11). + // + // (4), (5), and (10) are up to _PATH_MAX. The remaining fields have a + // small fixed maximum size, so 4*_PATH_MAX is plenty of scratch space. + // Note that non-cgroup mounts may have arbitrarily long (11), but we + // can skip those when parsing. + + l := newLineReader(fd, scratch, read) + + // Bytes written to out. + n := 0 + + for { + //incomplete := false + err := l.next() + if err == errIncompleteLine { + // An incomplete line is fine as long as it doesn't + // impede parsing the fields we need. It shouldn't be + // possible for any mount to use more than 3*PATH_MAX + // before (9) because there are two paths and all other + // earlier fields have bounded options. Only (11) has + // unbounded options. + } else if err == errEOF { + break + } else if err != nil { + return 0, err + } + + line := l.line() + + // Skip first four fields. + for range 4 { + i := bytealg.IndexByte(line, ' ') + if i < 0 { + return 0, errMalformedFile + } + line = line[i+1:] + } + + // (5) mount point: mount point relative to the process's root + i := bytealg.IndexByte(line, ' ') + if i < 0 { + return 0, errMalformedFile + } + mnt := line[:i] + line = line[i+1:] + + // Skip ahead past optional fields, delimited by " - ". + for { + i = bytealg.IndexByte(line, ' ') + if i < 0 { + return 0, errMalformedFile + } + if i+3 >= len(line) { + return 0, errMalformedFile + } + delim := line[i : i+3] + if string(delim) == " - " { + line = line[i+3:] + break + } + line = line[i+1:] + } + + // (9) filesystem type: name of filesystem of the form "type[.subtype]" + i = bytealg.IndexByte(line, ' ') + if i < 0 { + return 0, errMalformedFile + } + ftype := line[:i] + line = line[i+1:] + + if string(ftype) != "cgroup" && string(ftype) != "cgroup2" { + continue + } + + // As in findCPUPath, cgroup v1 with a CPU controller takes + // precendence over cgroup v2. + if string(ftype) == "cgroup2" { + // v2 hierarchy. + n, err = unescapePath(out, mnt) + if err != nil { + // Don't keep searching on error. The kernel + // should never produce broken escaping. + return n, err + } + // Keep searching, we might find a v1 hierarchy with a + // CPU controller, which takes precedence. + continue + } + + // (10) mount source: filesystem specific information or "none" + i = bytealg.IndexByte(line, ' ') + if i < 0 { + return 0, errMalformedFile + } + // Don't care about mount source. + line = line[i+1:] + + // (11) super options: per super block options + superOpt := line + + // v1 hierarchy + if containsCPU(superOpt) { + // Found a v1 CPU controller. This must be the + // only one, so we're done. + return unescapePath(out, mnt) + } + } + + if n == 0 { + // Found nothing. + return 0, ErrNoCgroup + } + + return n, nil +} + +var errInvalidEscape error = stringError("invalid path escape sequence") + +// unescapePath copies in to out, unescaping escape sequences generated by +// Linux's show_path. +// +// That is, '\', ' ', '\t', and '\n' are converted to octal escape sequences, +// like '\040' for space. +// +// out must be at least as large as in. +// +// Returns the number of bytes written to out. +// +// Also see escapePath in cgroup_linux_test.go. +func unescapePath(out []byte, in []byte) (int, error) { + // Not strictly necessary, but simplifies the implementation and will + // always hold in users. + if len(out) < len(in) { + throw("output too small") + } + + var outi, ini int + for ini < len(in) { + c := in[ini] + if c != '\\' { + out[outi] = c + outi++ + ini++ + continue + } + + // Start of escape sequence. + + // Escape sequence is always 4 characters: one slash and three + // digits. + if ini+3 >= len(in) { + return outi, errInvalidEscape + } + + var outc byte + for i := range 3 { + c := in[ini+1+i] + if c < '0' || c > '9' { + return outi, errInvalidEscape + } + + outc *= 8 + outc += c - '0' + } + + out[outi] = outc + outi++ + + ini += 4 + } + + return outi, nil +} diff --git a/src/internal/runtime/cgroup/cgroup_linux.go b/src/internal/runtime/cgroup/cgroup_linux.go index 7b35a9bc187..5e3ee0d2c2c 100644 --- a/src/internal/runtime/cgroup/cgroup_linux.go +++ b/src/internal/runtime/cgroup/cgroup_linux.go @@ -5,44 +5,7 @@ package cgroup import ( - "internal/bytealg" "internal/runtime/syscall/linux" - "internal/strconv" -) - -var ( - ErrNoCgroup error = stringError("not in a cgroup") - - errMalformedFile error = stringError("malformed file") -) - -const _PATH_MAX = 4096 - -const ( - // Required amount of scratch space for CPULimit. - // - // TODO(prattmic): This is shockingly large (~70KiB) due to the (very - // unlikely) combination of extremely long paths consisting mostly - // escaped characters. The scratch buffer ends up in .bss in package - // runtime, so it doesn't contribute to binary size and generally won't - // be faulted in, but it would still be nice to shrink this. A more - // complex parser that did not need to keep entire lines in memory - // could get away with much less. Alternatively, we could do a one-off - // mmap allocation for this buffer, which is only mapped larger if we - // actually need the extra space. - ScratchSize = PathSize + ParseSize - - // Required space to store a path of the cgroup in the filesystem. - PathSize = _PATH_MAX - - // /proc/self/mountinfo path escape sequences are 4 characters long, so - // a path consisting entirely of escaped characters could be 4 times - // larger. - escapedPathMax = 4 * _PATH_MAX - - // Required space to parse /proc/self/mountinfo and /proc/self/cgroup. - // See findCPUMount and findCPURelativePath. - ParseSize = 4 * escapedPathMax ) // Include explicit NUL to be sure we include it in the slice. @@ -52,15 +15,6 @@ const ( v1PeriodFile = "/cpu.cfs_period_us\x00" ) -// Version indicates the cgroup version. -type Version int - -const ( - VersionUnknown Version = iota - V1 - V2 -) - // CPU owns the FDs required to read the CPU limit from a cgroup. type CPU struct { version Version @@ -212,22 +166,6 @@ func readV1Number(fd int) (int64, error) { return parseV1Number(buf) } -func parseV1Number(buf []byte) (int64, error) { - // Ignore trailing newline. - i := bytealg.IndexByte(buf, '\n') - if i < 0 { - return 0, errMalformedFile - } - buf = buf[:i] - - val, err := strconv.ParseInt(string(buf), 10, 64) - if err != nil { - return 0, errMalformedFile - } - - return val, nil -} - // Returns CPU throughput limit, or ok false if there is no limit. func readV2Limit(fd int) (float64, bool, error) { // The format of the file is " \n" where quota and @@ -260,39 +198,6 @@ func readV2Limit(fd int) (float64, bool, error) { return parseV2Limit(buf) } -func parseV2Limit(buf []byte) (float64, bool, error) { - i := bytealg.IndexByte(buf, ' ') - if i < 0 { - return 0, false, errMalformedFile - } - - quotaStr := buf[:i] - if bytealg.Compare(quotaStr, []byte("max")) == 0 { - // No limit. - return 0, false, nil - } - - periodStr := buf[i+1:] - // Ignore trailing newline, if any. - i = bytealg.IndexByte(periodStr, '\n') - if i < 0 { - return 0, false, errMalformedFile - } - periodStr = periodStr[:i] - - quota, err := strconv.ParseInt(string(quotaStr), 10, 64) - if err != nil { - return 0, false, errMalformedFile - } - - period, err := strconv.ParseInt(string(periodStr), 10, 64) - if err != nil { - return 0, false, errMalformedFile - } - - return float64(quota) / float64(period), true, nil -} - // FindCPU finds the path to the CPU cgroup that this process is a member of // and places it in out. scratch is a scratch buffer for internal use. // @@ -364,118 +269,6 @@ func FindCPURelativePath(out []byte, scratch []byte) (int, Version, error) { return n, version, nil } -// Finds the path of the current process's CPU cgroup relative to the cgroup -// mount and writes it to out. -// -// Returns the number of bytes written and the cgroup version (1 or 2). -func parseCPURelativePath(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, Version, error) { - // The format of each line is - // - // hierarchy-ID:controller-list:cgroup-path - // - // controller-list is comma-separated. - // See man 5 cgroup for more details. - // - // cgroup v2 has hierarchy-ID 0. If a v1 hierarchy contains "cpu", that - // is the CPU controller. Otherwise the v2 hierarchy (if any) is the - // CPU controller. - // - // hierarchy-ID and controller-list have relatively small maximum - // sizes, and the path can be up to _PATH_MAX, so we need a bit more - // than 1 _PATH_MAX of scratch space. - - l := newLineReader(fd, scratch, read) - - // Bytes written to out. - n := 0 - - for { - err := l.next() - if err == errIncompleteLine { - // Don't allow incomplete lines. While in theory the - // incomplete line may be for a controller we don't - // care about, in practice all lines should be of - // similar length, so we should just have a buffer big - // enough for any. - return 0, 0, err - } else if err == errEOF { - break - } else if err != nil { - return 0, 0, err - } - - line := l.line() - - // The format of each line is - // - // hierarchy-ID:controller-list:cgroup-path - // - // controller-list is comma-separated. - // See man 5 cgroup for more details. - i := bytealg.IndexByte(line, ':') - if i < 0 { - return 0, 0, errMalformedFile - } - - hierarchy := line[:i] - line = line[i+1:] - - i = bytealg.IndexByte(line, ':') - if i < 0 { - return 0, 0, errMalformedFile - } - - controllers := line[:i] - line = line[i+1:] - - path := line - - if string(hierarchy) == "0" { - // v2 hierarchy. - n = copy(out, path) - // Keep searching, we might find a v1 hierarchy with a - // CPU controller, which takes precedence. - } else { - // v1 hierarchy - if containsCPU(controllers) { - // Found a v1 CPU controller. This must be the - // only one, so we're done. - return copy(out, path), V1, nil - } - } - } - - if n == 0 { - // Found nothing. - return 0, 0, ErrNoCgroup - } - - // Must be v2, v1 returns above. - return n, V2, nil -} - -// Returns true if comma-separated list b contains "cpu". -func containsCPU(b []byte) bool { - for len(b) > 0 { - i := bytealg.IndexByte(b, ',') - if i < 0 { - // Neither cmd/compile nor gccgo allocates for these string conversions. - return string(b) == "cpu" - } - - curr := b[:i] - rest := b[i+1:] - - if string(curr) == "cpu" { - return true - } - - b = rest - } - - return false -} - // FindCPUMountPoint finds the root of the CPU cgroup mount places it in out. // scratch is a scratch buffer for internal use. // @@ -505,206 +298,3 @@ func FindCPUMountPoint(out []byte, scratch []byte) (int, error) { return n, nil } - -// Returns the mount point for the cpu cgroup controller (v1 or v2) from -// /proc/self/mountinfo. -func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, error) { - // The format of each line is: - // - // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue - // (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) - // - // (1) mount ID: unique identifier of the mount (may be reused after umount) - // (2) parent ID: ID of parent (or of self for the top of the mount tree) - // (3) major:minor: value of st_dev for files on filesystem - // (4) root: root of the mount within the filesystem - // (5) mount point: mount point relative to the process's root - // (6) mount options: per mount options - // (7) optional fields: zero or more fields of the form "tag[:value]" - // (8) separator: marks the end of the optional fields - // (9) filesystem type: name of filesystem of the form "type[.subtype]" - // (10) mount source: filesystem specific information or "none" - // (11) super options: per super block options - // - // See man 5 proc_pid_mountinfo for more details. - // - // Note that emitted paths will not contain space, tab, newline, or - // carriage return. Those are escaped. See Linux show_mountinfo -> - // show_path. We must unescape before returning. - // - // We return the mount point (5) if the filesystem type (9) is cgroup2, - // or cgroup with "cpu" in the super options (11). - // - // (4), (5), and (10) are up to _PATH_MAX. The remaining fields have a - // small fixed maximum size, so 4*_PATH_MAX is plenty of scratch space. - // Note that non-cgroup mounts may have arbitrarily long (11), but we - // can skip those when parsing. - - l := newLineReader(fd, scratch, read) - - // Bytes written to out. - n := 0 - - for { - //incomplete := false - err := l.next() - if err == errIncompleteLine { - // An incomplete line is fine as long as it doesn't - // impede parsing the fields we need. It shouldn't be - // possible for any mount to use more than 3*PATH_MAX - // before (9) because there are two paths and all other - // earlier fields have bounded options. Only (11) has - // unbounded options. - } else if err == errEOF { - break - } else if err != nil { - return 0, err - } - - line := l.line() - - // Skip first four fields. - for range 4 { - i := bytealg.IndexByte(line, ' ') - if i < 0 { - return 0, errMalformedFile - } - line = line[i+1:] - } - - // (5) mount point: mount point relative to the process's root - i := bytealg.IndexByte(line, ' ') - if i < 0 { - return 0, errMalformedFile - } - mnt := line[:i] - line = line[i+1:] - - // Skip ahead past optional fields, delimited by " - ". - for { - i = bytealg.IndexByte(line, ' ') - if i < 0 { - return 0, errMalformedFile - } - if i+3 >= len(line) { - return 0, errMalformedFile - } - delim := line[i : i+3] - if string(delim) == " - " { - line = line[i+3:] - break - } - line = line[i+1:] - } - - // (9) filesystem type: name of filesystem of the form "type[.subtype]" - i = bytealg.IndexByte(line, ' ') - if i < 0 { - return 0, errMalformedFile - } - ftype := line[:i] - line = line[i+1:] - - if string(ftype) != "cgroup" && string(ftype) != "cgroup2" { - continue - } - - // As in findCPUPath, cgroup v1 with a CPU controller takes - // precendence over cgroup v2. - if string(ftype) == "cgroup2" { - // v2 hierarchy. - n, err = unescapePath(out, mnt) - if err != nil { - // Don't keep searching on error. The kernel - // should never produce broken escaping. - return n, err - } - // Keep searching, we might find a v1 hierarchy with a - // CPU controller, which takes precedence. - continue - } - - // (10) mount source: filesystem specific information or "none" - i = bytealg.IndexByte(line, ' ') - if i < 0 { - return 0, errMalformedFile - } - // Don't care about mount source. - line = line[i+1:] - - // (11) super options: per super block options - superOpt := line - - // v1 hierarchy - if containsCPU(superOpt) { - // Found a v1 CPU controller. This must be the - // only one, so we're done. - return unescapePath(out, mnt) - } - } - - if n == 0 { - // Found nothing. - return 0, ErrNoCgroup - } - - return n, nil -} - -var errInvalidEscape error = stringError("invalid path escape sequence") - -// unescapePath copies in to out, unescaping escape sequences generated by -// Linux's show_path. -// -// That is, '\', ' ', '\t', and '\n' are converted to octal escape sequences, -// like '\040' for space. -// -// out must be at least as large as in. -// -// Returns the number of bytes written to out. -// -// Also see escapePath in cgroup_linux_test.go. -func unescapePath(out []byte, in []byte) (int, error) { - // Not strictly necessary, but simplifies the implementation and will - // always hold in users. - if len(out) < len(in) { - throw("output too small") - } - - var outi, ini int - for ini < len(in) { - c := in[ini] - if c != '\\' { - out[outi] = c - outi++ - ini++ - continue - } - - // Start of escape sequence. - - // Escape sequence is always 4 characters: one slash and three - // digits. - if ini+3 >= len(in) { - return outi, errInvalidEscape - } - - var outc byte - for i := range 3 { - c := in[ini+1+i] - if c < '0' || c > '9' { - return outi, errInvalidEscape - } - - outc *= 8 - outc += c - '0' - } - - out[outi] = outc - outi++ - - ini += 4 - } - - return outi, nil -} diff --git a/src/internal/runtime/cgroup/cgroup_linux_test.go b/src/internal/runtime/cgroup/cgroup_test.go similarity index 100% rename from src/internal/runtime/cgroup/cgroup_linux_test.go rename to src/internal/runtime/cgroup/cgroup_test.go diff --git a/src/internal/runtime/cgroup/export_linux_test.go b/src/internal/runtime/cgroup/export_linux_test.go deleted file mode 100644 index 653fcd1b2fd..00000000000 --- a/src/internal/runtime/cgroup/export_linux_test.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2025 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cgroup - -var ContainsCPU = containsCPU - -var ParseV1Number = parseV1Number -var ParseV2Limit = parseV2Limit - -var ParseCPURelativePath = parseCPURelativePath -var ParseCPUMount = parseCPUMount - -var UnescapePath = unescapePath diff --git a/src/internal/runtime/cgroup/export_test.go b/src/internal/runtime/cgroup/export_test.go index 200e5aee121..55acdc0877e 100644 --- a/src/internal/runtime/cgroup/export_test.go +++ b/src/internal/runtime/cgroup/export_test.go @@ -22,3 +22,13 @@ var ( ErrEOF = errEOF ErrIncompleteLine = errIncompleteLine ) + +var ContainsCPU = containsCPU + +var ParseV1Number = parseV1Number +var ParseV2Limit = parseV2Limit + +var ParseCPURelativePath = parseCPURelativePath +var ParseCPUMount = parseCPUMount + +var UnescapePath = unescapePath