Files
windows_exporter/internal/log/rotate_windows_test.go
T
2026-10-15 16:40:05 +00:00

170 lines
4.7 KiB
Go

// SPDX-License-Identifier: Apache-2.0
//
// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
package log
import (
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
"golang.org/x/sys/windows"
)
// openWithoutDeleteSharing opens path like a log shipper that doesn't allow
// delete sharing, which blocks renaming the file.
func openWithoutDeleteSharing(t *testing.T, path string) windows.Handle {
t.Helper()
pathPtr, err := windows.UTF16PtrFromString(path)
require.NoError(t, err)
handle, err := windows.CreateFile(pathPtr, windows.GENERIC_READ, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0)
require.NoError(t, err)
return handle
}
func TestRotatingFileBlockedByOtherProcess(t *testing.T) {
t.Parallel()
path := filepath.Join(t.TempDir(), "windows_exporter.log")
now := time.Date(2026, 10, 15, 14, 3, 5, 0, time.UTC)
file, err := newRotatingFile(path, 10, 0, false, func() time.Time { return now })
require.NoError(t, err)
t.Cleanup(func() { _ = file.Close() })
write(t, file, "1234567890")
blocker := openWithoutDeleteSharing(t, path)
// The rename fails while the file is held: the line is appended instead.
write(t, file, "while blocked\n")
require.Equal(t, []string{"windows_exporter.log"}, dirEntries(t, filepath.Dir(path)))
require.NoError(t, windows.CloseHandle(blocker))
now = now.Add(file.retryInterval)
write(t, file, "after release\n")
require.NoError(t, file.Close())
require.Equal(t, "1234567890while blocked\n", readFile(t, filepath.Join(filepath.Dir(path), "windows_exporter-2026-10-15T14-04-05.000.log")))
require.Equal(t, "after release\n", readFile(t, path))
}
func TestOpenLogFileAllowsRenameAndDelete(t *testing.T) {
t.Parallel()
dir := t.TempDir()
path := filepath.Join(dir, "windows_exporter.log")
file, err := openLogFile(path)
require.NoError(t, err)
t.Cleanup(func() { _ = file.Close() })
_, err = file.WriteString("first\n")
require.NoError(t, err)
// An admin can move the file while the exporter writes to it.
moved := filepath.Join(dir, "moved.log")
require.NoError(t, os.Rename(path, moved))
_, err = file.WriteString("second\n")
require.NoError(t, err)
require.Equal(t, "first\nsecond\n", readFile(t, moved))
// And delete it.
require.NoError(t, os.Remove(moved))
}
func TestOpenLogFileAppends(t *testing.T) {
t.Parallel()
path := filepath.Join(t.TempDir(), "windows_exporter.log")
require.NoError(t, os.WriteFile(path, []byte("existing\n"), 0o600))
file, err := openLogFile(path)
require.NoError(t, err)
_, err = file.WriteString("appended\n")
require.NoError(t, err)
require.NoError(t, file.Close())
require.Equal(t, "existing\nappended\n", readFile(t, path))
}
func TestRotatingFileRecoversWhenMovedOrDeleted(t *testing.T) {
t.Parallel()
for name, remove := range map[string]func(path string) error{
"moved": func(path string) error { return os.Rename(path, path+".moved") },
"deleted": os.Remove,
} {
t.Run(name, func(t *testing.T) {
t.Parallel()
path := filepath.Join(t.TempDir(), "windows_exporter.log")
now := time.Date(2026, 10, 15, 14, 3, 5, 0, time.UTC)
file, err := newRotatingFile(path, 1<<20, 0, false, func() time.Time { return now })
require.NoError(t, err)
t.Cleanup(func() { _ = file.Close() })
write(t, file, "before\n")
// Share-delete lets an admin move or delete the file while it is open.
require.NoError(t, remove(path))
now = now.Add(file.retryInterval)
write(t, file, "after\n")
require.NoError(t, file.Close())
require.Equal(t, "after\n", readFile(t, path))
})
}
}
func TestSameFile(t *testing.T) {
t.Parallel()
dir := t.TempDir()
path := filepath.Join(dir, "windows_exporter.log")
file, err := openLogFile(path)
require.NoError(t, err)
t.Cleanup(func() { _ = file.Close() })
same, err := sameFile(file, path)
require.NoError(t, err)
require.True(t, same)
require.NoError(t, os.Rename(path, filepath.Join(dir, "moved.log")))
require.NoError(t, os.WriteFile(path, []byte("other"), 0o600))
same, err = sameFile(file, path)
require.NoError(t, err)
require.False(t, same, "a new file at the path is not the open file")
}