* Copyright (c) 2025 Bocloud Technologies Co., Ltd.
* installer is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* <http://license.coscl.org.cn/MulanPSL2>
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package timezone
import (
"net"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
"github.com/agiledragon/gomonkey/v2"
"github.com/stretchr/testify/assert"
"gopkg.openfuyao.cn/bkeadm/pkg/global"
"gopkg.openfuyao.cn/bkeadm/pkg/server"
"gopkg.openfuyao.cn/bkeadm/utils"
"gopkg.openfuyao.cn/cluster-api-provider-bke/common/ntp"
)
const (
testIPv4SegmentA = 127
testIPv4SegmentB = 0
testIPv4SegmentC = 0
testIPv4SegmentD = 1
)
const (
testFileModeReadOnly = 0644
testFileModeExec = 0755
)
var testLoopbackIP = net.IPv4(
testIPv4SegmentA,
testIPv4SegmentB,
testIPv4SegmentC,
testIPv4SegmentD,
)
func TestFindTimezoneFileNotFound(t *testing.T) {
result, err := findTimezoneFile("Invalid/Timezone")
assert.Error(t, err)
assert.Empty(t, result)
}
func TestCopyFileSuccess(t *testing.T) {
src := t.TempDir() + "/src"
dst := t.TempDir() + "/dst"
content := []byte("test content")
err := os.WriteFile(src, content, testFileModeReadOnly)
assert.NoError(t, err)
err = copyFile(src, dst)
assert.NoError(t, err)
result, err := os.ReadFile(dst)
assert.NoError(t, err)
assert.Equal(t, content, result)
}
func TestCopyFileReadError(t *testing.T) {
dst := t.TempDir() + "/dst"
err := copyFile("/nonexistent/file", dst)
assert.Error(t, err)
}
func TestCopyFileWriteError(t *testing.T) {
src := t.TempDir() + "/src"
dst := t.TempDir() + "/dst"
content := []byte("test content")
err := os.WriteFile(src, content, testFileModeReadOnly)
assert.NoError(t, err)
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(os.WriteFile, func(string, []byte, os.FileMode) error {
return assert.AnError
})
err = copyFile(src, dst)
assert.Error(t, err)
}
func TestNTPServerLocalName(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(startLocalNTPServer, func(string) (string, error) {
return testLoopbackIP.String() + ":123", nil
})
result, err := NTPServer("local", testLoopbackIP.String(), false)
assert.NoError(t, err)
assert.NotEmpty(t, result)
}
func TestNTPServerExternalSuccess(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(ntp.Date, func(string) error {
return nil
})
result, err := NTPServer("pool.ntp.org", testLoopbackIP.String(), false)
assert.NoError(t, err)
assert.Equal(t, "pool.ntp.org", result)
}
func TestNTPServerExternalFailOffline(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(ntp.Date, func(string) error {
return assert.AnError
})
result, err := NTPServer("pool.ntp.org", testLoopbackIP.String(), false)
assert.Error(t, err)
assert.Empty(t, result)
}
func TestNTPServerDefaultServer(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(ntp.Date, func(string) error {
return assert.AnError
})
patches.ApplyFunc(startLocalNTPServer, func(string) (string, error) {
return testLoopbackIP.String() + ":123", nil
})
result, err := NTPServer("cn.pool.ntp.org:123", testLoopbackIP.String(), false)
assert.NoError(t, err)
assert.NotEmpty(t, result)
}
func TestNTPServerUnreachableWithOnline(t *testing.T) {
sleepPatches := gomonkey.ApplyFunc(time.Sleep, func(time.Duration) {})
defer sleepPatches.Reset()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(ntp.Date, func(string) error {
return assert.AnError
})
result, err := NTPServer("unreachable.server", testLoopbackIP.String(), true)
assert.Error(t, err)
assert.Empty(t, result)
}
func TestTryConnectExternalNTPSuccess(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(ntp.Date, func(string) error {
return nil
})
result, err := tryConnectExternalNTP("pool.ntp.org", false)
assert.NoError(t, err)
assert.Equal(t, "pool.ntp.org", result)
}
func TestTryConnectExternalNTPFailOffline(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(ntp.Date, func(string) error {
return assert.AnError
})
result, err := tryConnectExternalNTP("pool.ntp.org", false)
assert.Error(t, err)
assert.Empty(t, result)
}
func TestTryConnectExternalNTPRetrySuccess(t *testing.T) {
sleepPatches := gomonkey.ApplyFunc(time.Sleep, func(time.Duration) {})
defer sleepPatches.Reset()
callCount := 0
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(ntp.Date, func(string) error {
callCount++
if callCount < 2 {
return assert.AnError
}
return nil
})
result, err := tryConnectExternalNTP("pool.ntp.org", true)
assert.NoError(t, err)
assert.Equal(t, "pool.ntp.org", result)
}
func TestRetryNTPConnectionAllFail(t *testing.T) {
sleepPatches := gomonkey.ApplyFunc(time.Sleep, func(time.Duration) {})
defer sleepPatches.Reset()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(ntp.Date, func(string) error {
return assert.AnError
})
result, err := retryNTPConnection("pool.ntp.org")
assert.Error(t, err)
assert.Empty(t, result)
}
func TestStartLocalNTPServerSuccess(t *testing.T) {
originalK8s := global.K8s
global.K8s = nil
defer func() { global.K8s = originalK8s }()
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(utils.ExecPath, func() (string, error) {
return "/fake/path", nil
})
patches.ApplyFunc(os.Getenv, func(string) string {
return ""
})
patches.ApplyFunc(os.Getpid, func() int {
return 12345
})
patches.ApplyFunc((*exec.Cmd).Start, func(*exec.Cmd) error {
return nil
})
patches.ApplyFunc(server.TryConnectNTPServer, func(string) bool {
return true
})
result, err := startLocalNTPServer(testLoopbackIP.String())
assert.NoError(t, err)
assert.Contains(t, result, testLoopbackIP.String())
}
func TestStartLocalNTPServerExecPathError(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(utils.ExecPath, func() (string, error) {
return "", assert.AnError
})
result, err := startLocalNTPServer(testLoopbackIP.String())
assert.Error(t, err)
assert.Empty(t, result)
}
func TestSetTimeZoneAlreadySet(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(utils.Exists, func(string) bool {
return true
})
patches.ApplyFunc(os.ReadFile, func(string) ([]byte, error) {
return []byte("Asia/Shanghai"), nil
})
err := SetTimeZone()
assert.NoError(t, err)
}
func TestSetTimeZoneReadTimezoneError(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(utils.Exists, func(string) bool {
return true
})
patches.ApplyFunc(os.ReadFile, func(string) ([]byte, error) {
return nil, assert.AnError
})
err := SetTimeZone()
assert.Error(t, err)
}
func TestSetTimeZoneFindTimezoneFileError(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(utils.Exists, func(string) bool {
return false
})
patches.ApplyFunc(findTimezoneFile, func(string) (string, error) {
return "", assert.AnError
})
err := SetTimeZone()
assert.Error(t, err)
}
func TestSetTimeZoneSymlinkSuccess(t *testing.T) {
tempDir := t.TempDir()
zoneInfoPath := filepath.Join(tempDir, "Asia/Shanghai")
err := os.MkdirAll(filepath.Dir(zoneInfoPath), testFileModeExec)
assert.NoError(t, err)
err = os.WriteFile(zoneInfoPath, []byte("tzdata"), testFileModeReadOnly)
assert.NoError(t, err)
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(utils.Exists, func(path string) bool {
return path == timezonePath
})
patches.ApplyFunc(os.ReadFile, func(string) ([]byte, error) {
return []byte("America/New_York"), nil
})
patches.ApplyFunc(findTimezoneFile, func(zone string) (string, error) {
return zoneInfoPath, nil
})
patches.ApplyFunc(os.Stat, func(string) (os.FileInfo, error) {
return nil, assert.AnError
})
patches.ApplyFunc(os.Rename, func(string, string) error {
return nil
})
patches.ApplyFunc(os.Symlink, func(string, string) error {
return nil
})
patches.ApplyFunc(os.WriteFile, func(string, []byte, os.FileMode) error {
return nil
})
err = SetTimeZone()
assert.NoError(t, err)
}
func TestSetTimeZoneSymlinkErrorCopySuccess(t *testing.T) {
copyFileCalled := false
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(utils.Exists, func(path string) bool {
return path == timezonePath
})
patches.ApplyFunc(os.ReadFile, func(string) ([]byte, error) {
return []byte("America/New_York"), nil
})
patches.ApplyFunc(findTimezoneFile, func(zone string) (string, error) {
return "/usr/share/zoneinfo/Asia/Shanghai", nil
})
patches.ApplyFunc(os.Stat, func(string) (os.FileInfo, error) {
return nil, nil
})
patches.ApplyFunc(os.Rename, func(string, string) error {
return nil
})
patches.ApplyFunc(os.Symlink, func(target, link string) error {
return assert.AnError
})
patches.ApplyFunc(copyFile, func(src, dst string) error {
copyFileCalled = true
return nil
})
patches.ApplyFunc(os.WriteFile, func(path string, data []byte, perm os.FileMode) error {
return nil
})
err := SetTimeZone()
assert.NoError(t, err)
assert.True(t, copyFileCalled, "copyFile should have been called")
}
func TestSetTimeZoneCopyFileError(t *testing.T) {
tempDir := t.TempDir()
zoneInfoPath := filepath.Join(tempDir, "Asia/Shanghai")
err := os.MkdirAll(filepath.Dir(zoneInfoPath), testFileModeExec)
assert.NoError(t, err)
err = os.WriteFile(zoneInfoPath, []byte("tzdata"), testFileModeReadOnly)
assert.NoError(t, err)
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(utils.Exists, func(path string) bool {
return path == timezonePath || path == localtimePath
})
patches.ApplyFunc(os.ReadFile, func(string) ([]byte, error) {
return []byte("America/New_York"), nil
})
patches.ApplyFunc(findTimezoneFile, func(zone string) (string, error) {
return zoneInfoPath, nil
})
patches.ApplyFunc(os.Stat, func(string) (os.FileInfo, error) {
return nil, nil
})
patches.ApplyFunc(os.Symlink, func(string, string) error {
return assert.AnError
})
patches.ApplyFunc(os.Rename, func(string, string) error {
return nil
})
patches.ApplyFunc(copyFile, func(string, string) error {
return assert.AnError
})
err = SetTimeZone()
assert.Error(t, err)
}
func TestSetTimeZoneWriteFileError(t *testing.T) {
tempDir := t.TempDir()
zoneInfoPath := filepath.Join(tempDir, "Asia/Shanghai")
err := os.MkdirAll(filepath.Dir(zoneInfoPath), testFileModeExec)
assert.NoError(t, err)
err = os.WriteFile(zoneInfoPath, []byte("tzdata"), testFileModeReadOnly)
assert.NoError(t, err)
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(utils.Exists, func(path string) bool {
return false
})
patches.ApplyFunc(findTimezoneFile, func(zone string) (string, error) {
return zoneInfoPath, nil
})
patches.ApplyFunc(os.Stat, func(string) (os.FileInfo, error) {
return nil, assert.AnError
})
patches.ApplyFunc(os.Rename, func(string, string) error {
return nil
})
patches.ApplyFunc(os.Symlink, func(string, string) error {
return assert.AnError
})
patches.ApplyFunc(copyFile, func(string, string) error {
return nil
})
writeFileCalled := false
patches.ApplyFunc(os.WriteFile, func(string, []byte, os.FileMode) error {
writeFileCalled = true
return assert.AnError
})
err = SetTimeZone()
assert.Error(t, err)
assert.True(t, writeFileCalled)
}
func TestSetTimeZoneRenameError(t *testing.T) {
tempDir := t.TempDir()
zoneInfoPath := filepath.Join(tempDir, "Asia/Shanghai")
err := os.MkdirAll(filepath.Dir(zoneInfoPath), testFileModeExec)
assert.NoError(t, err)
err = os.WriteFile(zoneInfoPath, []byte("tzdata"), testFileModeReadOnly)
assert.NoError(t, err)
patches := gomonkey.NewPatches()
defer patches.Reset()
patches.ApplyFunc(utils.Exists, func(path string) bool {
return path == timezonePath
})
patches.ApplyFunc(os.ReadFile, func(string) ([]byte, error) {
return []byte("America/New_York"), nil
})
patches.ApplyFunc(findTimezoneFile, func(zone string) (string, error) {
return zoneInfoPath, nil
})
patches.ApplyFunc(os.Stat, func(string) (os.FileInfo, error) {
return nil, nil
})
patches.ApplyFunc(os.Rename, func(string, string) error {
return nil
})
patches.ApplyFunc(os.Symlink, func(string, string) error {
return nil
})
patches.ApplyFunc(os.WriteFile, func(string, []byte, os.FileMode) error {
return nil
})
err = SetTimeZone()
assert.NoError(t, err)
}