From 36fe94d9d2a089c2ac7633173b4852c81251aa7e Mon Sep 17 00:00:00 2001 From: zhaofangxun Date: Wed, 19 Aug 2026 17:53:55 +0800 Subject: [PATCH] test: add 118 unit tests across 12 modules + register 3 pkgs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Add 115 new unit tests across 12 core modules: graph (loader DAG algorithm), keybinding1/util, common/fileutil, display1/utils, system/resource_ctl, inputdevices1/iso639, common/sessionmsg, calltrace, system/airplane_mode1, system/scheduler, grub_gfx, and image_effect1. The last five test pure functions inside packages that build with cgo (via loader -> go-gir -> glib/gdk). 2. Register display1/utils, common/fileutil, common/cpuinfo, common/scale, display1/brightness, and image_effect1 in the Makefile TEST list so they run under `make test` (cpuinfo/scale/ brightness already had test files; this adds them to the gate, bringing the gated total to 118 cases: 115 new + 3 pre-existing). Influence: - The 8 pure-Go packages (graph, display1/utils, common/fileutil, keybinding1/util, inputdevices1/iso639, common/sessionmsg, common/cpuinfo, common/scale) can be verified directly with `go test`. The 7 cgo packages (system/resource_ctl, display1/brightness, calltrace, system/airplane_mode1, system/scheduler, grub_gfx, image_effect1) need the dde-daemon cgo build dependency stack installed (glib/gdk via loader -> go-gir). - Validate with `make test` (all 15 packages are in the TEST list) and generate coverage reports with `make test-coverage`. 补充 12 个核心模块单元测试 + 补登 3 个既有测试包门控,共 118 用例。 1. 为 12 个核心模块新增 115 个单元测试:graph(loader DAG 核心算法)、keybinding1/util、common/fileutil、display1/utils、system/resource_ctl、inputdevices1/iso639、common/sessionmsg、calltrace、system/airplane_mode1、system/scheduler、grub_gfx、image_effect1。后 5 个为 cgo 依赖包(经 loader→go-gir→glib/gdk),本轮测其包内纯函数/纯算法。 2. 在 Makefile 的 TEST 列表追加 display1/utils、common/fileutil、common/cpuinfo、common/scale、display1/brightness、image_effect1,纳入 `make test` 门控(其中 cpuinfo/scale/brightness 既有测试文件早已存在,本轮补登门控,门控用例累计 118:115 新增 + 3 既有)。 影响: - 纯 Go 的 8 个包(graph/display1/utils/common/fileutil/keybinding1/util/inputdevices1/iso639/common/sessionmsg/common/cpuinfo/common/scale)可直接 `go test`;7 个 cgo 包(system/resource_ctl、display1/brightness、calltrace、system/airplane_mode1、system/scheduler、grub_gfx、image_effect1)需 dde-daemon 标准 cgo 构建依赖栈(经 loader→go-gir 依赖 glib/gdk 等)。 - 可通过 `make test`(15 包均在 TEST 列表)验证,并用 `make test-coverage` 生成覆盖率报告。 --- Makefile | 6 + calltrace/calltrace_test.go | 112 ++++++++ common/fileutil/fileutil_test.go | 126 +++++++++ common/sessionmsg/sessionmsg_test.go | 125 +++++++++ display1/utils/utils_test.go | 72 +++++ graph/graph_test.go | 286 ++++++++++++++++++++ grub_gfx/grub_gfx_test.go | 71 +++++ image_effect1/image_effect_test.go | 83 ++++++ inputdevices1/iso639/iso639_test.go | 67 +++++ keybinding1/util/util_test.go | 109 ++++++++ system/airplane_mode1/airplane_mode_test.go | 101 +++++++ system/resource_ctl/cgroup_test.go | 74 +++++ system/scheduler/scheduler_test.go | 125 +++++++++ 13 files changed, 1357 insertions(+) create mode 100644 calltrace/calltrace_test.go create mode 100644 common/fileutil/fileutil_test.go create mode 100644 common/sessionmsg/sessionmsg_test.go create mode 100644 display1/utils/utils_test.go create mode 100644 graph/graph_test.go create mode 100644 grub_gfx/grub_gfx_test.go create mode 100644 image_effect1/image_effect_test.go create mode 100644 inputdevices1/iso639/iso639_test.go create mode 100644 keybinding1/util/util_test.go create mode 100644 system/airplane_mode1/airplane_mode_test.go create mode 100644 system/resource_ctl/cgroup_test.go create mode 100644 system/scheduler/scheduler_test.go diff --git a/Makefile b/Makefile index 83cb08811..463c23fd9 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,8 @@ TEST = \ ${GOPKG_PREFIX}/clipboard1 \ ${GOPKG_PREFIX}/clipboard1/mocks \ ${GOPKG_PREFIX}/common/bluetooth \ + ${GOPKG_PREFIX}/common/cpuinfo \ + ${GOPKG_PREFIX}/common/scale \ ${GOPKG_PREFIX}/common/dsync \ ${GOPKG_PREFIX}/common/sessionmsg \ ${GOPKG_PREFIX}/dbus \ @@ -51,6 +53,7 @@ TEST = \ ${GOPKG_PREFIX}/grub_common \ ${GOPKG_PREFIX}/grub_gfx \ ${GOPKG_PREFIX}/housekeeping \ + ${GOPKG_PREFIX}/image_effect1 \ ${GOPKG_PREFIX}/inputdevices1 \ ${GOPKG_PREFIX}/inputdevices1/iso639 \ ${GOPKG_PREFIX}/keybinding1 \ @@ -88,6 +91,9 @@ TEST = \ ${GOPKG_PREFIX}/x_event_monitor1 \ ${GOPKG_PREFIX}/bin/default-file-manager \ ${GOPKG_PREFIX}/display1 \ + ${GOPKG_PREFIX}/display1/brightness \ + ${GOPKG_PREFIX}/display1/utils \ + ${GOPKG_PREFIX}/common/fileutil \ #${GOPKG_PREFIX}/timedate1/zoneinfo \ BINARIES = \ diff --git a/calltrace/calltrace_test.go b/calltrace/calltrace_test.go new file mode 100644 index 000000000..b3b0868b8 --- /dev/null +++ b/calltrace/calltrace_test.go @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package calltrace + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStoi64(t *testing.T) { + assert.Equal(t, int64(123), stoi64("123")) + assert.Equal(t, int64(0), stoi64("")) + assert.Equal(t, int64(0), stoi64("abc")) + assert.Equal(t, int64(-42), stoi64("-42")) +} + +func TestCpuTimeInfo_Total(t *testing.T) { + info := &cpuTimeInfo{ + utime: 10, + stime: 20, + cutime: 5, + cstime: 3, + nice: 2, + start: 100, + hertz: 100, + } + assert.Equal(t, int64(40), info.Total()) +} + +func TestCpuTimeInfo_Total_Zero(t *testing.T) { + info := &cpuTimeInfo{} + assert.Equal(t, int64(0), info.Total()) +} + +func TestCpuTimeInfo_Percentage(t *testing.T) { + // start=200, hertz=100 → start/hertz=2, seconds = uptime - 2 + // Total=100, Total/hertz=1, Percentage = 100 * (1 / (uptime-2)) + info := &cpuTimeInfo{ + utime: 50, + stime: 50, + cutime: 0, + cstime: 0, + nice: 0, + start: 200, + hertz: 100, + } + // uptime comes from /proc/uptime; can't control, but we can verify + // the formula doesn't panic and returns a float (likely 0 in test env + // because /proc/uptime read may fail or return 0 → division by zero). + // Instead test the pure arithmetic directly: + assert.Equal(t, int64(100), info.Total()) +} + +func TestGetInterge(t *testing.T) { + // format: "RssAnon: 100 kB" → split → ["RssAnon:", "100", "kB"] + v, err := getInterge("RssAnon: 100 kB") + assert.NoError(t, err) + assert.Equal(t, int64(100), v) + + v, err = getInterge("VmPTE: 8 kB") + assert.NoError(t, err) + assert.Equal(t, int64(8), v) +} + +func TestGetInterge_BadFormat(t *testing.T) { + // 只有两个字段(不足 3 个)→ error + _, err := getInterge("VmPTE: 100") + assert.Error(t, err) + + // 中间不是数字 → error + _, err = getInterge("VmPTE: abc kB") + assert.Error(t, err) +} + +func TestSumMemByFile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "status") + content := "Name:\ttest\n" + + "RssAnon: 100 kB\n" + + "VmPTE: 8 kB\n" + + "VmPMD: 2 kB\n" + + "Other: 999 kB\n" + require.NoError(t, os.WriteFile(path, []byte(content), 0600)) + + total, err := sumMemByFile(path) + assert.NoError(t, err) + // 100 + 8 + 2 = 110 + assert.Equal(t, int64(110), total) +} + +func TestSumMemByFile_MissingFields(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "status") + // 只有 RssAnon,缺 VmPTE/VmPMD → 只累加 RssAnon + content := "RssAnon: 50 kB\n" + require.NoError(t, os.WriteFile(path, []byte(content), 0600)) + + total, err := sumMemByFile(path) + assert.NoError(t, err) + assert.Equal(t, int64(50), total) +} + +func TestSumMemByFile_NonExistent(t *testing.T) { + _, err := sumMemByFile("/nonexistent/path/to/status") + assert.Error(t, err) +} diff --git a/common/fileutil/fileutil_test.go b/common/fileutil/fileutil_test.go new file mode 100644 index 000000000..32de47b98 --- /dev/null +++ b/common/fileutil/fileutil_test.go @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package fileutil + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSafeReadFile_RegularFile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "data.txt") + content := []byte("hello dde-daemon") + require.NoError(t, os.WriteFile(path, content, 0600)) + + got, err := SafeReadFile(path) + assert.NoError(t, err) + assert.Equal(t, content, got) +} + +func TestSafeReadFile_NonExistent(t *testing.T) { + dir := t.TempDir() + _, err := SafeReadFile(filepath.Join(dir, "missing")) + assert.Error(t, err) +} + +func TestSafeReadFile_SymlinkRejected(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "target") + require.NoError(t, os.WriteFile(target, []byte("x"), 0600)) + link := filepath.Join(dir, "link") + require.NoError(t, os.Symlink(target, link)) + + _, err := SafeReadFile(link) + assert.Error(t, err) + assert.Contains(t, err.Error(), "symlink") +} + +func TestSafeReadFile_DirectoryRejected(t *testing.T) { + dir := t.TempDir() + // 目录不是普通文件,应被拒绝 + _, err := SafeReadFile(dir) + assert.Error(t, err) + assert.Contains(t, err.Error(), "not a regular file") +} + +func TestSafeWriteFile_CreateNew(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "new.txt") + content := []byte("fresh content") + + err := SafeWriteFile(path, content, 0600) + assert.NoError(t, err) + + // 权限:owner 读写位必置(用掩码校验,对仅清 group/other 位的 umask 稳健) + info, err := os.Stat(path) + require.NoError(t, err) + assert.True(t, info.Mode().IsRegular()) + assert.Equal(t, os.FileMode(0600), info.Mode().Perm()&os.FileMode(0600)) + + got, err := SafeReadFile(path) + assert.NoError(t, err) + assert.Equal(t, content, got) +} + +func TestSafeWriteFile_OverwriteRegular(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "exists.txt") + require.NoError(t, os.WriteFile(path, []byte("old"), 0600)) + + err := SafeWriteFile(path, []byte("new"), 0600) + assert.NoError(t, err) + + got, err := SafeReadFile(path) + assert.NoError(t, err) + assert.Equal(t, []byte("new"), got) +} + +func TestSafeWriteFile_SymlinkRejected(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "target") + require.NoError(t, os.WriteFile(target, []byte("x"), 0600)) + link := filepath.Join(dir, "link") + require.NoError(t, os.Symlink(target, link)) + + err := SafeWriteFile(link, []byte("y"), 0600) + assert.Error(t, err) + assert.Contains(t, err.Error(), "symlink") + + // 被拒绝后,符号链接目标内容不应被改写 + got, err := os.ReadFile(target) + require.NoError(t, err) + assert.Equal(t, []byte("x"), got) +} + +func TestSafeWriteFile_DirectoryRejected(t *testing.T) { + // 目标已存在且为目录(非普通文件),写侧应拒绝,与读侧对称 + dir := t.TempDir() + err := SafeWriteFile(dir, []byte("x"), 0600) + assert.Error(t, err) + assert.Contains(t, err.Error(), "not a regular file") +} + +func TestSafeWriteFile_NonExistentParent(t *testing.T) { + dir := t.TempDir() + // 父目录不存在,O_CREAT|O_EXCL 创建应失败 + err := SafeWriteFile(filepath.Join(dir, "sub", "file"), []byte("z"), 0600) + assert.Error(t, err) +} + +func TestSafeWriteFile_RoundTrip(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "round.txt") + payload := []byte{0x00, 0x01, 0xFF, 'A', '\n', '\t'} + + require.NoError(t, SafeWriteFile(path, payload, 0600)) + got, err := SafeReadFile(path) + assert.NoError(t, err) + assert.Equal(t, payload, got) +} diff --git a/common/sessionmsg/sessionmsg_test.go b/common/sessionmsg/sessionmsg_test.go new file mode 100644 index 000000000..80e4ae6ff --- /dev/null +++ b/common/sessionmsg/sessionmsg_test.go @@ -0,0 +1,125 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package sessionmsg + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBodyNotify_MessageType(t *testing.T) { + var b BodyNotify + assert.Equal(t, MessageTypeNotify, b.MessageType()) +} + +func TestNewMessage_Notify(t *testing.T) { + body := &BodyNotify{Icon: "dialog-info", AppName: "test-app"} + m := NewMessage(true, body) + + assert.Equal(t, MessageTypeNotify, m.Type) + assert.True(t, m.OnlyActive) + assert.True(t, m.Body == body) +} + +func TestNewMessage_OnlyActiveFalse(t *testing.T) { + body := &BodyNotify{Icon: "x"} + m := NewMessage(false, body) + + assert.False(t, m.OnlyActive) + assert.Equal(t, MessageTypeNotify, m.Type) + assert.True(t, m.Body == body) +} + +func TestMessage_UnmarshalJSON_Notify(t *testing.T) { + raw := `{"OnlyActive":true,"Type":1,"Body":{"Icon":"dialog-info","AppName":"test-app","ExpireTimeout":5000}}` + var m Message + err := m.UnmarshalJSON([]byte(raw)) + require.NoError(t, err) + + assert.Equal(t, MessageTypeNotify, m.Type) + assert.True(t, m.OnlyActive) + + bn, ok := m.Body.(*BodyNotify) + assert.True(t, ok, "Body should be *BodyNotify") + assert.Equal(t, "dialog-info", bn.Icon) + assert.Equal(t, "test-app", bn.AppName) + assert.Equal(t, 5000, bn.ExpireTimeout) +} + +func TestMessage_UnmarshalJSON_UnknownType(t *testing.T) { + // Type=99 不是已知类型,switch 不匹配,Body 保持 nil,无错误 + raw := `{"OnlyActive":false,"Type":99,"Body":{}}` + var m Message + err := m.UnmarshalJSON([]byte(raw)) + assert.NoError(t, err) + assert.Equal(t, MessageType(99), m.Type) + assert.False(t, m.OnlyActive) + assert.Nil(t, m.Body) +} + +func TestMessage_UnmarshalJSON_InvalidJSON(t *testing.T) { + raw := `{invalid json` + var m Message + err := m.UnmarshalJSON([]byte(raw)) + assert.Error(t, err) +} + +func TestMessage_UnmarshalJSON_BodyUnmarshalError(t *testing.T) { + // Type=1 触发 BodyNotify 解析,但 ExpireTimeout 传字符串无法转为 int + raw := `{"OnlyActive":true,"Type":1,"Body":{"ExpireTimeout":"not-a-number"}}` + var m Message + err := m.UnmarshalJSON([]byte(raw)) + assert.Error(t, err) + assert.Nil(t, m.Body) +} + +func TestMessage_MarshalUnmarshal_RoundTrip(t *testing.T) { + orig := NewMessage(true, &BodyNotify{ + Icon: "dialog-warning", + AppName: "roundtrip-app", + ExpireTimeout: -1, + }) + data, err := json.Marshal(orig) + require.NoError(t, err) + + var decoded Message + err = decoded.UnmarshalJSON(data) + require.NoError(t, err) + assert.Equal(t, orig.Type, decoded.Type) + assert.Equal(t, orig.OnlyActive, decoded.OnlyActive) + + bn, ok := decoded.Body.(*BodyNotify) + assert.True(t, ok) + assert.Equal(t, "dialog-warning", bn.Icon) + assert.Equal(t, "roundtrip-app", bn.AppName) + assert.Equal(t, -1, bn.ExpireTimeout) +} + +func TestLocalizeStr_String_Nil(t *testing.T) { + var ls *LocalizeStr + assert.Equal(t, "", ls.String()) +} + +func TestLocalizeStr_String_NoArgs(t *testing.T) { + ls := &LocalizeStr{Format: "hello world"} + assert.Equal(t, "hello world", ls.String()) +} + +func TestLocalizeStr_String_WithArgs(t *testing.T) { + ls := &LocalizeStr{Format: "Hello %s, welcome to %s", Args: []string{"Alice", "DDE"}} + assert.Equal(t, "Hello Alice, welcome to DDE", ls.String()) +} + +func TestLocalizeStr_String_SingleArg(t *testing.T) { + ls := &LocalizeStr{Format: "value is %s", Args: []string{"42"}} + assert.Equal(t, "value is 42", ls.String()) +} + +func TestMessageType_Constants(t *testing.T) { + assert.Equal(t, MessageType(1), MessageTypeNotify) +} diff --git a/display1/utils/utils_test.go b/display1/utils/utils_test.go new file mode 100644 index 000000000..6dec7e972 --- /dev/null +++ b/display1/utils/utils_test.go @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package utils + +import ( + "encoding/base64" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEncodeEdidBase64(t *testing.T) { + // 与标准 base64 编码结果一致(确认使用 StdEncoding,非 URLEncoding) + edid := []byte{0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00} + assert.Equal(t, base64.StdEncoding.EncodeToString(edid), EncodeEdidBase64(edid)) + + // 手工可验证的小样本 + assert.Equal(t, "/w==", EncodeEdidBase64([]byte{0xFF})) + assert.Equal(t, "AAA=", EncodeEdidBase64([]byte{0x00, 0x00})) +} + +func TestEncodeEdidBase64_StdEncodingLocked(t *testing.T) { + // 0xFB,0xFF 经 StdEncoding 编码为 "+/8="(含 '+' 与 '/'), + // URLEncoding 则为 "-_8="(含 '-' 与 '_')。用字面量锁死 StdEncoding, + // 防止被误改为 URLEncoding。 + assert.Equal(t, "+/8=", EncodeEdidBase64([]byte{0xFB, 0xFF})) + assert.Contains(t, EncodeEdidBase64([]byte{0xFB, 0xFF}), "+") + assert.Contains(t, EncodeEdidBase64([]byte{0xFB, 0xFF}), "/") +} + +func TestDecodeEdidBase64(t *testing.T) { + got, err := DecodeEdidBase64("/w==") + assert.NoError(t, err) + assert.Equal(t, []byte{0xFF}, got) + + got, err = DecodeEdidBase64("AAA=") + assert.NoError(t, err) + assert.Equal(t, []byte{0x00, 0x00}, got) +} + +func TestEncodeDecodeEdidBase64_RoundTrip(t *testing.T) { + // 模拟 128 字节 EDID 数据 + edid := make([]byte, 128) + for i := range edid { + edid[i] = byte(i) + } + + encoded := EncodeEdidBase64(edid) + decoded, err := DecodeEdidBase64(encoded) + require.NoError(t, err) + assert.Equal(t, edid, decoded) +} + +func TestDecodeEdidBase64_Empty(t *testing.T) { + got, err := DecodeEdidBase64("") + assert.NoError(t, err) + assert.Equal(t, []byte{}, got) +} + +func TestDecodeEdidBase64_Invalid(t *testing.T) { + // 非法 base64 输入应返回错误 + _, err := DecodeEdidBase64("!!!not-base64!!!") + assert.Error(t, err) +} + +func TestEncodeEdidBase64_Empty(t *testing.T) { + assert.Equal(t, "", EncodeEdidBase64(nil)) + assert.Equal(t, "", EncodeEdidBase64([]byte{})) +} diff --git a/graph/graph_test.go b/graph/graph_test.go new file mode 100644 index 000000000..419330b33 --- /dev/null +++ b/graph/graph_test.go @@ -0,0 +1,286 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package graph + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNew(t *testing.T) { + g := New() + assert.NotNil(t, g) + assert.Equal(t, 0, g.GetNodeSize()) + assert.Equal(t, "Graph is empty.", g.String()) +} + +func TestInit(t *testing.T) { + g := New() + assert.True(t, g.AddNode(NewNode("a"))) + assert.Equal(t, 1, g.GetNodeSize()) + // Init 以 *d = *New() 重置图,清空所有节点 + g.Init() + assert.Equal(t, 0, g.GetNodeSize()) +} + +func TestNewNode(t *testing.T) { + n := NewNode("a") + assert.Equal(t, "a", n.ID) + assert.Equal(t, "white", n.Color) + assert.NotNil(t, n.WeightTo) + assert.NotNil(t, n.WeightFrom) + assert.Equal(t, 0, len(n.WeightTo)) + assert.Equal(t, 0, len(n.WeightFrom)) +} + +func TestAddNode(t *testing.T) { + g := New() + // nil 节点被拒绝 + assert.False(t, g.AddNode(nil)) + assert.Equal(t, 0, g.GetNodeSize()) + + // 首次添加成功 + assert.True(t, g.AddNode(NewNode("a"))) + assert.Equal(t, 1, g.GetNodeSize()) + + // 重复 ID 被拒绝 + assert.False(t, g.AddNode(NewNode("a"))) + assert.Equal(t, 1, g.GetNodeSize()) + + // 同一指针重复添加被拒绝 + nd := NewNode("b") + assert.True(t, g.AddNode(nd)) + assert.False(t, g.AddNode(nd)) + assert.Equal(t, 2, g.GetNodeSize()) +} + +func TestGetNodeByID(t *testing.T) { + g := New() + g.AddNode(NewNode("a")) + g.AddNode(NewNode("b")) + + assert.Equal(t, "a", g.GetNodeByID("a").ID) + assert.Equal(t, "b", g.GetNodeByID("b").ID) + assert.Nil(t, g.GetNodeByID("missing")) +} + +func TestConnectAndGetEdgeWeight(t *testing.T) { + g := New() + a, b, c := NewNode("a"), NewNode("b"), NewNode("c") + + // nil 端点不产生边、不添加节点 + g.Connect(nil, b, 1) + assert.Equal(t, 0, g.GetNodeSize()) + + g.Connect(a, b, 1.5) + g.Connect(b, c, 2.5) + + assert.Equal(t, 3, g.GetNodeSize()) + assert.Equal(t, float32(1.5), g.GetEdgeWeight(a, b)) + assert.Equal(t, float32(2.5), g.GetEdgeWeight(b, c)) + // 不存在的边权重为 0 + assert.Equal(t, float32(0), g.GetEdgeWeight(a, c)) + // nil 端点权重为 0 + assert.Equal(t, float32(0), g.GetEdgeWeight(nil, b)) +} + +func TestConnectDuplicateNodeReusesExisting(t *testing.T) { + g := New() + a, b := NewNode("a"), NewNode("b") + g.Connect(a, b, 1) + + // 用重复 ID 的新指针连接,应复用已有节点、更新边权重,而非新增节点 + aDup := NewNode("a") + g.Connect(aDup, b, 9) + assert.Equal(t, 2, g.GetNodeSize()) + assert.Equal(t, float32(9), g.GetEdgeWeight(a, b)) +} + +func TestGetEdges(t *testing.T) { + g := New() + a, b, c := NewNode("a"), NewNode("b"), NewNode("c") + g.Connect(a, b, 1.5) + g.Connect(b, c, 2.5) + + edges := g.GetEdges() + assert.Equal(t, 2, len(edges)) + + weights := map[string]float32{} + for _, e := range edges { + weights[e.Src.ID+"->"+e.Dst.ID] = e.Weight + } + assert.Equal(t, float32(1.5), weights["a->b"]) + assert.Equal(t, float32(2.5), weights["b->c"]) + _, ok := weights["a->c"] + assert.False(t, ok) +} + +func TestUpdateEdgeWeight(t *testing.T) { + g := New() + a, b := NewNode("a"), NewNode("b") + g.Connect(a, b, 1) + assert.Equal(t, float32(1), g.GetEdgeWeight(a, b)) + + g.UpdateEdgeWeight(a, b, 7) + assert.Equal(t, float32(7), g.GetEdgeWeight(a, b)) + + // nil 端点为空操作 + g.UpdateEdgeWeight(nil, b, 3) + assert.Equal(t, float32(7), g.GetEdgeWeight(a, b)) +} + +func TestDeleteEdge(t *testing.T) { + g := New() + a, b, c := NewNode("a"), NewNode("b"), NewNode("c") + g.Connect(a, b, 1) + g.Connect(b, c, 2) + + g.DeleteEdge(a, b) + assert.Equal(t, float32(0), g.GetEdgeWeight(a, b)) + // 其它边不受影响 + assert.Equal(t, float32(2), g.GetEdgeWeight(b, c)) + // 节点不随边删除 + assert.Equal(t, 3, g.GetNodeSize()) + + // nil 端点为空操作 + g.DeleteEdge(nil, b) + assert.Equal(t, float32(2), g.GetEdgeWeight(b, c)) +} + +func TestDeleteNode(t *testing.T) { + g := New() + a, b, c := NewNode("a"), NewNode("b"), NewNode("c") + g.Connect(a, b, 1) + g.Connect(b, c, 2) + g.Connect(c, a, 3) + + g.DeleteNode(b) + assert.Equal(t, 2, g.GetNodeSize()) + assert.Nil(t, g.GetNodeByID("b")) + // 其它节点指向 b 的边被清除(a.WeightTo[b] 被 DeleteNode 移除) + assert.Equal(t, float32(0), g.GetEdgeWeight(a, b)) + // 与被删节点无关的边保留 + assert.Equal(t, float32(3), g.GetEdgeWeight(c, a)) + // 图中不再有任何涉及 b 的边 + for _, e := range g.GetEdges() { + assert.NotEqual(t, "b", e.Src.ID) + assert.NotEqual(t, "b", e.Dst.ID) + } + + // nil 为空操作 + g.DeleteNode(nil) + assert.Equal(t, 2, g.GetNodeSize()) +} + +func TestTopologicalDag(t *testing.T) { + // a -> b -> c,应为 DAG,结果中 a 早于 b,b 早于 c + g := New() + a, b, c := NewNode("a"), NewNode("b"), NewNode("c") + g.Connect(a, b, 1) + g.Connect(b, c, 1) + + result, ok := g.TopologicalDag() + assert.True(t, ok) + assert.Equal(t, 3, len(result)) + + indexOf := func(id string) int { + for i, nd := range result { + if nd.ID == id { + return i + } + } + return -1 + } + ia, ib, ic := indexOf("a"), indexOf("b"), indexOf("c") + assert.NotEqual(t, -1, ia) + assert.NotEqual(t, -1, ib) + assert.NotEqual(t, -1, ic) + assert.True(t, ia < ib) + assert.True(t, ib < ic) +} + +func TestTopologicalDag_Empty(t *testing.T) { + g := New() + result, ok := g.TopologicalDag() + assert.True(t, ok) + assert.Equal(t, 0, len(result)) +} + +func TestTopologicalDag_Cycle(t *testing.T) { + // a <-> b 形成环,应返回 false 且结果为 nil + g := New() + a, b := NewNode("a"), NewNode("b") + g.Connect(a, b, 1) + g.Connect(b, a, 1) + + result, ok := g.TopologicalDag() + assert.False(t, ok) + assert.Nil(t, result) +} + +func TestClone(t *testing.T) { + // 连通图 a -> b -> c(Clone 仅从首个可达分量深拷贝,须用连通图) + g := New() + a, b, c := NewNode("a"), NewNode("b"), NewNode("c") + g.Connect(a, b, 1.5) + g.Connect(b, c, 2.5) + + cloned := g.Clone() + assert.NotNil(t, cloned) + assert.Equal(t, 3, cloned.GetNodeSize()) + + // 克隆图保留权重与结构 + ca, cb := cloned.GetNodeByID("a"), cloned.GetNodeByID("b") + cc := cloned.GetNodeByID("c") + assert.Equal(t, float32(1.5), cloned.GetEdgeWeight(ca, cb)) + assert.Equal(t, float32(2.5), cloned.GetEdgeWeight(cb, cc)) + + // 克隆出的节点是全新对象,与原图节点相互独立 + assert.True(t, a != ca) + + // 克隆图自身仍是 DAG,可被拓扑排序 + result, ok := cloned.TopologicalDag() + assert.True(t, ok) + assert.Equal(t, 3, len(result)) +} + +func TestClone_Empty(t *testing.T) { + g := New() + cloned := g.Clone() + assert.NotNil(t, cloned) + assert.Equal(t, 0, cloned.GetNodeSize()) +} + +func TestNodesGet(t *testing.T) { + ns := Nodes{NewNode("x"), NewNode("y")} + assert.Equal(t, "x", ns.Get("x").ID) + assert.Equal(t, "y", ns.Get("y").ID) + assert.Nil(t, ns.Get("z")) +} + +func TestNodeString(t *testing.T) { + n := NewNode("a") + s := n.String() + assert.Contains(t, s, "a") + assert.Contains(t, s, "0 Outgoing") + assert.Contains(t, s, "0 Incoming") + + g := New() + a, b := NewNode("a"), NewNode("b") + g.Connect(a, b, 1) + s = a.String() + assert.Contains(t, s, "1 Outgoing") +} + +func TestDataString_NonEmpty(t *testing.T) { + g := New() + a, b := NewNode("a"), NewNode("b") + g.Connect(a, b, 1) + s := g.String() + assert.Contains(t, s, "Graph has 2 Nodes") + assert.Contains(t, s, "a") + assert.Contains(t, s, "b") +} diff --git a/grub_gfx/grub_gfx_test.go b/grub_gfx/grub_gfx_test.go new file mode 100644 index 000000000..175b8b9fd --- /dev/null +++ b/grub_gfx/grub_gfx_test.go @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package grub_gfx + +import ( + "testing" + + "github.com/linuxdeepin/dde-daemon/grub_common" + + "github.com/stretchr/testify/assert" +) + +func TestDetectCache_Equal_Match(t *testing.T) { + c := DetectCache{ + EdidsHash: "abc123", + MaxGfxmode: "1920x1080", + } + assert.True(t, c.equal("abc123", grub_common.Gfxmode{Width: 1920, Height: 1080})) +} + +func TestDetectCache_Equal_HashMismatch(t *testing.T) { + c := DetectCache{ + EdidsHash: "abc123", + MaxGfxmode: "1920x1080", + } + assert.False(t, c.equal("different", grub_common.Gfxmode{Width: 1920, Height: 1080})) +} + +func TestDetectCache_Equal_GfxmodeMismatch(t *testing.T) { + c := DetectCache{ + EdidsHash: "abc123", + MaxGfxmode: "1920x1080", + } + assert.False(t, c.equal("abc123", grub_common.Gfxmode{Width: 1280, Height: 720})) +} + +func TestDetectCache_Equal_BothMismatch(t *testing.T) { + c := DetectCache{ + EdidsHash: "abc123", + MaxGfxmode: "1920x1080", + } + assert.False(t, c.equal("wrong", grub_common.Gfxmode{Width: 800, Height: 600})) +} + +func TestDetectCache_Equal_EmptyCache(t *testing.T) { + c := DetectCache{} + assert.False(t, c.equal("abc123", grub_common.Gfxmode{Width: 1920, Height: 1080})) +} + +func TestDetectCache_Equal_EmptyHash(t *testing.T) { + c := DetectCache{ + EdidsHash: "", + MaxGfxmode: "1920x1080", + } + assert.True(t, c.equal("", grub_common.Gfxmode{Width: 1920, Height: 1080})) + assert.False(t, c.equal("nonempty", grub_common.Gfxmode{Width: 1920, Height: 1080})) +} + +func TestSaveDetectCache_Invalid(t *testing.T) { + // 空的 EdidsHash 或 MaxGfxmode 应返回错误 + err := saveDetectCache(DetectCache{}) + assert.Error(t, err) + + err = saveDetectCache(DetectCache{EdidsHash: "abc", MaxGfxmode: ""}) + assert.Error(t, err) + + err = saveDetectCache(DetectCache{EdidsHash: "", MaxGfxmode: "1920x1080"}) + assert.Error(t, err) +} diff --git a/image_effect1/image_effect_test.go b/image_effect1/image_effect_test.go new file mode 100644 index 000000000..c111d42cb --- /dev/null +++ b/image_effect1/image_effect_test.go @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package image_effect + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestModTimeEqual_SameTime(t *testing.T) { + t1 := time.Date(2026, 1, 15, 10, 30, 0, 500000, time.UTC) + t2 := time.Date(2026, 1, 15, 10, 30, 0, 500000, time.UTC) + assert.True(t, modTimeEqual(t1, t2)) +} + +func TestModTimeEqual_DifferentTime(t *testing.T) { + t1 := time.Date(2026, 1, 15, 10, 30, 0, 0, time.UTC) + t2 := time.Date(2026, 1, 15, 10, 30, 5, 0, time.UTC) + assert.False(t, modTimeEqual(t1, t2)) +} + +func TestModTimeEqual_TruncatesNanoseconds(t *testing.T) { + // modTimeEqual 仅比较到微秒(/1000),同微秒但不同纳秒应判等 + t1 := time.Date(2026, 1, 15, 10, 30, 0, 500999, time.UTC) + t2 := time.Date(2026, 1, 15, 10, 30, 0, 500000, time.UTC) + assert.True(t, modTimeEqual(t1, t2)) +} + +func TestModTimeEqual_DifferentMicrosecond(t *testing.T) { + t1 := time.Date(2026, 1, 15, 10, 30, 0, 500000, time.UTC) + t2 := time.Date(2026, 1, 15, 10, 30, 0, 600000, time.UTC) + assert.False(t, modTimeEqual(t1, t2)) +} + +func TestModTimeEqual_BothZero(t *testing.T) { + assert.True(t, modTimeEqual(time.Time{}, time.Time{})) +} + +func TestSetFileModTime(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "file.txt") + require.NoError(t, os.WriteFile(path, []byte("x"), 0600)) + + target := time.Date(2025, 6, 1, 12, 0, 0, 0, time.UTC) + err := setFileModTime(path, target) + assert.NoError(t, err) + + info, err := os.Stat(path) + require.NoError(t, err) + assert.Equal(t, target.Unix(), info.ModTime().Unix()) +} + +func TestSetFileModTime_NonExistent(t *testing.T) { + err := setFileModTime("/nonexistent/file/path.txt", time.Now()) + assert.Error(t, err) +} + +func TestGetOutputFile(t *testing.T) { + // getOutputFile = filepath.Join(cacheDir, effect, md5(filename)+ext(filename)) + out := getOutputFile("blur", "/home/user/photo.png") + assert.Contains(t, out, cacheDir) + assert.Contains(t, out, "blur") + assert.Contains(t, out, ".png") +} + +func TestGetOutputFile_NoExtension(t *testing.T) { + out := getOutputFile("effect", "noextfile") + assert.Contains(t, out, cacheDir) + assert.Contains(t, out, "effect") + // 无扩展名,filepath.Ext 返回 "" + assert.False(t, containsExt(out)) +} + +func containsExt(path string) bool { + return filepath.Ext(path) != "" +} diff --git a/inputdevices1/iso639/iso639_test.go b/inputdevices1/iso639/iso639_test.go new file mode 100644 index 000000000..53de9dc8c --- /dev/null +++ b/inputdevices1/iso639/iso639_test.go @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package iso639 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConvertA2ToA3_SameTAndB(t *testing.T) { + // A3T == A3B 时仅返回一个元素 + // "aa" → A3B="aar", A3T="aar" + result := ConvertA2ToA3("aa") + assert.Equal(t, []string{"aar"}, result) +} + +func TestConvertA2ToA3_DifferentTAndB(t *testing.T) { + // A3T != A3B 时返回两个元素 [A3T, A3B] + // "sq" → A3T="sqi", A3B="alb" + result := ConvertA2ToA3("sq") + assert.Equal(t, []string{"sqi", "alb"}, result) +} + +func TestConvertA2ToA3_DifferentTAndB_Zh(t *testing.T) { + // "zh" → A3T="zho", A3B="chi" + result := ConvertA2ToA3("zh") + assert.Equal(t, []string{"zho", "chi"}, result) +} + +func TestConvertA2ToA3_NotFound(t *testing.T) { + // 不存在的 ISO 639-1 代码,返回 nil + result := ConvertA2ToA3("zz") + assert.Nil(t, result) +} + +func TestConvertA2ToA3_EmptyString(t *testing.T) { + // 源码未做空输入守卫:空串匹配首个 A2 为空的条目(ace,A3T==A3B), + // 返回 []string{"ace"} 而非 nil。锁定当前真实行为。 + result := ConvertA2ToA3("") + assert.Equal(t, []string{"ace"}, result) +} + +func TestConvertA2ToA3_CommonLanguages(t *testing.T) { + // 验证常见语言代码(A3T == A3B 的情况) + for _, tc := range []struct { + a2 string + a3 string + }{ + {"aa", "aar"}, + {"en", "eng"}, + {"fr", "fra"}, // A3T="fra", A3B="fre" → 不同,返回两个 + } { + result := ConvertA2ToA3(tc.a2) + assert.NotEqual(t, 0, len(result), "a2=%s", tc.a2) + assert.Equal(t, tc.a3, result[0], "a2=%s", tc.a2) + } +} + +func TestConvertA2ToA3_FrenchReturnsTwoCodes(t *testing.T) { + // "fr" → A3T="fra", A3B="fre",返回两个 + result := ConvertA2ToA3("fr") + assert.Equal(t, []string{"fra", "fre"}, result) + assert.Equal(t, 2, len(result)) +} diff --git a/keybinding1/util/util_test.go b/keybinding1/util/util_test.go new file mode 100644 index 000000000..e4323a889 --- /dev/null +++ b/keybinding1/util/util_test.go @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package util + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMarshalJSON_NoHTMLEscape(t *testing.T) { + // SetEscapeHTML(false) 应保留 < > 等字符,不转义为 \u003c + got, err := MarshalJSON(&KWinAccel{ + Id: "x", + Keystrokes: []string{"Print"}, + }) + assert.NoError(t, err) + // enc.Encode 会追加一个换行 + assert.Equal(t, `{"Id":"x","Accels":["Print"]}`+"\n", got) + // 关键断言:尖括号未被 HTML 转义 + assert.True(t, strings.Contains(got, "Print")) + assert.False(t, strings.Contains(got, `\u003c`)) +} + +func TestMarshalJSON_WithDefault(t *testing.T) { + got, err := MarshalJSON(&KWinAccel{ + Id: "y", + Keystrokes: []string{"a"}, + DefaultKeystrokes: []string{"b"}, + }) + assert.NoError(t, err) + assert.Equal(t, `{"Id":"y","Accels":["a"],"Default":["b"]}`+"\n", got) +} + +func TestMarshalJSON_DefaultOmittedWhenEmpty(t *testing.T) { + got, err := MarshalJSON(&KWinAccel{ + Id: "z", + Keystrokes: []string{"a"}, + DefaultKeystrokes: nil, + }) + assert.NoError(t, err) + assert.Equal(t, `{"Id":"z","Accels":["a"]}`+"\n", got) + assert.False(t, strings.Contains(got, "Default")) +} + +func TestMarshalJSON_Error(t *testing.T) { + // 不可序列化的值(chan)应使 enc.Encode 返回错误,函数返回 "" + got, err := MarshalJSON(make(chan int)) + assert.Error(t, err) + assert.Equal(t, "", got) +} + +func TestKWinAccel_FixFiltersKeystrokes(t *testing.T) { + kwa := &KWinAccel{ + Keystrokes: []string{"a", "", "b", ""}, + DefaultKeystrokes: []string{"x", "y z", "", "z", " w"}, + } + kwa.fix() + + assert.Equal(t, []string{"a", "b"}, kwa.Keystrokes) + // 含空格或为空的默认值应被剔除 + assert.Equal(t, []string{"x", "z"}, kwa.DefaultKeystrokes) +} + +func TestKWinAccel_FixAllEmpty(t *testing.T) { + kwa := &KWinAccel{ + Keystrokes: []string{"", ""}, + DefaultKeystrokes: []string{"", " "}, + } + kwa.fix() + + assert.Equal(t, 0, len(kwa.Keystrokes)) + assert.Equal(t, 0, len(kwa.DefaultKeystrokes)) +} + +func TestKWinAccel_FixIdempotentOnClean(t *testing.T) { + kwa := &KWinAccel{ + Keystrokes: []string{"a", "b"}, + DefaultKeystrokes: []string{"c"}, + } + kwa.fix() + assert.Equal(t, []string{"a", "b"}, kwa.Keystrokes) + assert.Equal(t, []string{"c"}, kwa.DefaultKeystrokes) +} + +func TestKWinAccel_FixKeystrokesKeepsSpaces(t *testing.T) { + // 锁定 fix() 的非对称语义:Keystrokes 仅过滤空串、保留含空格值; + // DefaultKeystrokes 同时过滤空串与含空格值。 + kwa := &KWinAccel{ + Keystrokes: []string{"a b", "c", ""}, + DefaultKeystrokes: []string{"d e", "f", ""}, + } + kwa.fix() + // Keystrokes 保留含空格的 "a b",仅剔除空串 + assert.Equal(t, []string{"a b", "c"}, kwa.Keystrokes) + // DefaultKeystrokes 剔除含空格的 "d e" 与空串 + assert.Equal(t, []string{"f"}, kwa.DefaultKeystrokes) +} + +func TestKWinAccel_FixOnZeroValue(t *testing.T) { + // 零值(nil 切片)入参不应 panic,结果为 nil + kwa := &KWinAccel{} + kwa.fix() // 若 panic 则用例直接失败 + assert.Nil(t, kwa.Keystrokes) + assert.Nil(t, kwa.DefaultKeystrokes) +} diff --git a/system/airplane_mode1/airplane_mode_test.go b/system/airplane_mode1/airplane_mode_test.go new file mode 100644 index 000000000..f9c3cfc9c --- /dev/null +++ b/system/airplane_mode1/airplane_mode_test.go @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package airplane_mode + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRadioAction_ToRfkillState(t *testing.T) { + assert.Equal(t, rfkillStateUnblock, UnblockRadioAction.ToRfkillState()) + assert.Equal(t, rfkillStateBlock, BlockRadioAction.ToRfkillState()) + // 未映射的动作返回零值(rfkillStateUnblock = 0) + assert.Equal(t, rfkillState(0), NoneRadioAction.ToRfkillState()) + assert.Equal(t, rfkillState(0), ListRadioAction.ToRfkillState()) + assert.Equal(t, rfkillState(0), MonitorRadioAction.ToRfkillState()) +} + +func TestRadioAction_String(t *testing.T) { + assert.Equal(t, "block", BlockRadioAction.String()) + assert.Equal(t, "unblock", UnblockRadioAction.String()) + assert.Equal(t, "list", ListRadioAction.String()) + assert.Equal(t, "event", MonitorRadioAction.String()) + // 未映射的动作返回空串 + assert.Equal(t, "", NoneRadioAction.String()) +} + +func TestNewConfig(t *testing.T) { + cfg := NewConfig() + assert.NotNil(t, cfg) + // 新建配置,所有模块默认未阻塞 + assert.False(t, cfg.GetBlocked(rfkillTypeWifi)) + assert.False(t, cfg.GetBlocked(rfkillTypeBT)) + assert.False(t, cfg.GetBlocked(rfkillTypeAll)) +} + +func TestConfig_SetGetBlocked(t *testing.T) { + cfg := NewConfig() + + cfg.SetBlocked(rfkillTypeWifi, true) + assert.True(t, cfg.GetBlocked(rfkillTypeWifi)) + // 其它模块不受影响 + assert.False(t, cfg.GetBlocked(rfkillTypeBT)) + + cfg.SetBlocked(rfkillTypeBT, true) + assert.True(t, cfg.GetBlocked(rfkillTypeWifi)) + assert.True(t, cfg.GetBlocked(rfkillTypeBT)) + + // 解除阻塞 + cfg.SetBlocked(rfkillTypeWifi, false) + assert.False(t, cfg.GetBlocked(rfkillTypeWifi)) + assert.True(t, cfg.GetBlocked(rfkillTypeBT)) +} + +func TestConfig_GetBlocked_DefaultFalse(t *testing.T) { + cfg := NewConfig() + // 未设置过的模块,GetBlocked 返回 false(默认未阻塞) + assert.False(t, cfg.GetBlocked(rfkillTypeAll)) + assert.False(t, cfg.GetBlocked(rfkillTypeWifi)) +} + +func TestReadFile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "test.txt") + require.NoError(t, os.WriteFile(path, []byte(" hello world \n"), 0600)) + + got, err := readFile(path) + assert.NoError(t, err) + assert.Equal(t, "hello world", got) +} + +func TestReadFile_NonExistent(t *testing.T) { + _, err := readFile("/nonexistent/file/path") + assert.Error(t, err) +} + +func TestReadFile_EmptyFile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "empty.txt") + require.NoError(t, os.WriteFile(path, []byte(""), 0600)) + + got, err := readFile(path) + assert.NoError(t, err) + assert.Equal(t, "", got) +} + +func TestIsLittleEndian(t *testing.T) { + // 不关心具体平台结果,只验证不 panic 且返回布尔值 + _ = isLittleEndian() +} + +func TestGetByteOrder(t *testing.T) { + // 验证返回非 nil 的 ByteOrder,不 panic + _ = getByteOrder() +} diff --git a/system/resource_ctl/cgroup_test.go b/system/resource_ctl/cgroup_test.go new file mode 100644 index 000000000..f047c53c8 --- /dev/null +++ b/system/resource_ctl/cgroup_test.go @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package resource_control + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestToSystemdPath(t *testing.T) { + assert.Equal(t, "/sys/fs/cgroup/systemd/app-dde-x.scope", + toSystemdPath("app-dde-x.scope")) + assert.Equal(t, "/sys/fs/cgroup/systemd/", toSystemdPath("")) +} + +func TestToCpuPath(t *testing.T) { + assert.Equal(t, "/sys/fs/cgroup/cpu/app-dde-x.scope", + toCpuPath("app-dde-x.scope")) + assert.Equal(t, "/sys/fs/cgroup/cpu/", toCpuPath("")) +} + +func TestToMemPath(t *testing.T) { + assert.Equal(t, "/sys/fs/cgroup/memory/app-dde-x.scope", + toMemPath("app-dde-x.scope")) + assert.Equal(t, "/sys/fs/cgroup/memory/", toMemPath("")) +} + +func TestGetTasksFromFile(t *testing.T) { + dir := t.TempDir() + + // 多行(含结尾换行):按 "\n" 切分,末尾产生一个空元素 + p1 := filepath.Join(dir, "tasks_trailing") + require.NoError(t, os.WriteFile(p1, []byte("10\n20\n"), 0600)) + tasks, err := getTasksFromFile(p1) + assert.NoError(t, err) + assert.Equal(t, [][]byte{[]byte("10"), []byte("20"), []byte("")}, tasks) + + // 多行(无结尾换行) + p2 := filepath.Join(dir, "tasks_notrail") + require.NoError(t, os.WriteFile(p2, []byte("10\n20"), 0600)) + tasks, err = getTasksFromFile(p2) + assert.NoError(t, err) + assert.Equal(t, [][]byte{[]byte("10"), []byte("20")}, tasks) + + // 单行无换行 + p3 := filepath.Join(dir, "tasks_single") + require.NoError(t, os.WriteFile(p3, []byte("42"), 0600)) + tasks, err = getTasksFromFile(p3) + assert.NoError(t, err) + assert.Equal(t, [][]byte{[]byte("42")}, tasks) + + // 空文件:得到单个空元素 + p4 := filepath.Join(dir, "tasks_empty") + require.NoError(t, os.WriteFile(p4, []byte(""), 0600)) + tasks, err = getTasksFromFile(p4) + assert.NoError(t, err) + assert.Equal(t, [][]byte{[]byte("")}, tasks) +} + +func TestGetTasksFromFile_NonExistent(t *testing.T) { + dir := t.TempDir() + tasks, err := getTasksFromFile(filepath.Join(dir, "missing")) + assert.Error(t, err) + assert.Nil(t, tasks) + // 错误应包含来源路径上下文 + assert.Contains(t, err.Error(), "failed to get tasks from") + assert.Contains(t, err.Error(), "missing") +} diff --git a/system/scheduler/scheduler_test.go b/system/scheduler/scheduler_test.go new file mode 100644 index 000000000..4cded658a --- /dev/null +++ b/system/scheduler/scheduler_test.go @@ -0,0 +1,125 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package scheduler + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestU32SliceContains(t *testing.T) { + slice := []uint32{1, 10, 100, 1000} + assert.True(t, u32SliceContains(slice, 1)) + assert.True(t, u32SliceContains(slice, 1000)) + assert.True(t, u32SliceContains(slice, 10)) + assert.False(t, u32SliceContains(slice, 5)) + assert.False(t, u32SliceContains(slice, 0)) +} + +func TestU32SliceContains_Empty(t *testing.T) { + assert.False(t, u32SliceContains(nil, 1)) + assert.False(t, u32SliceContains([]uint32{}, 1)) +} + +func TestConfig_GetPriority_ByFullPath(t *testing.T) { + cfg := &config{ + Processes: map[string]*priorityCfg{ + "/usr/bin/foo": {CPU: 5}, + }, + } + p := cfg.getPriority("/usr/bin/foo") + assert.NotNil(t, p) + assert.Equal(t, 5, p.CPU) +} + +func TestConfig_GetPriority_ByBaseName(t *testing.T) { + // 完整路径未命中时,回退用 filepath.Base(exe) 查找 + cfg := &config{ + Processes: map[string]*priorityCfg{ + "foo": {CPU: 9}, + }, + } + p := cfg.getPriority("/usr/bin/foo") + assert.NotNil(t, p) + assert.Equal(t, 9, p.CPU) + + p = cfg.getPriority("foo") + assert.NotNil(t, p) + assert.Equal(t, 9, p.CPU) +} + +func TestConfig_GetPriority_NotFound(t *testing.T) { + cfg := &config{ + Processes: map[string]*priorityCfg{ + "bar": {CPU: 1}, + }, + } + assert.Nil(t, cfg.getPriority("/usr/bin/foo")) + assert.Nil(t, cfg.getPriority("foo")) +} + +func TestConfig_GetPriority_EmptyProcesses(t *testing.T) { + cfg := &config{Processes: map[string]*priorityCfg{}} + assert.Nil(t, cfg.getPriority("/usr/bin/anything")) +} + +func TestLoadConfigAux(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "config.json") + content := `{ + "processes": { + "/usr/bin/foo": {"cpu": 5}, + "bar": {"cpu": 3} + }, + "enabled": true, + "procMonitorEnabled": false + }` + require.NoError(t, os.WriteFile(path, []byte(content), 0600)) + + cfg, err := loadConfigAux(path) + require.NoError(t, err) + assert.NotNil(t, cfg) + assert.Equal(t, path, cfg.filename) + assert.True(t, cfg.Enabled) + assert.False(t, cfg.ProcMonitorEnabled) + assert.Equal(t, 5, cfg.Processes["/usr/bin/foo"].CPU) + assert.Equal(t, 3, cfg.Processes["bar"].CPU) + + // getPriority 能命中 + p := cfg.getPriority("/usr/bin/foo") + assert.Equal(t, 5, p.CPU) + p = cfg.getPriority("/usr/bin/bar") + assert.Equal(t, 3, p.CPU) +} + +func TestLoadConfigAux_NonExistent(t *testing.T) { + _, err := loadConfigAux("/nonexistent/config.json") + assert.Error(t, err) +} + +func TestLoadConfigAux_InvalidJSON(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "config.json") + require.NoError(t, os.WriteFile(path, []byte("{invalid json"), 0600)) + + _, err := loadConfigAux(path) + assert.Error(t, err) +} + +func TestLoadConfigAux_EmptyProcesses(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "config.json") + content := `{"processes": {}, "enabled": false}` + require.NoError(t, os.WriteFile(path, []byte(content), 0600)) + + cfg, err := loadConfigAux(path) + require.NoError(t, err) + assert.NotNil(t, cfg.Processes) + assert.False(t, cfg.Enabled) +}