mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
e7725e0e11
* Fix unvalidated path warning in cirfile.go * Adding test * move dir path vars into global scope, set wave home in test if not set already * separate validation code into own testable func * do removal of cir file in cleanup * rename test file names * flock should return err if waiting without done fn * update comment * use timeout context * remove redundant validation * add more error messages * use filepath.join instead * validate when creating cirfile too * support .ptyout.cf, etc. * remove validation as cirfile is "trusted", since it only accepts pre-validated input * revert to passing nil context for flock in the Create file case
304 lines
8.7 KiB
Go
304 lines
8.7 KiB
Go
// Copyright 2023, Command Line Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package cirfile
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func validateFileSize(t *testing.T, name string, size int) {
|
|
finfo, err := os.Stat(name)
|
|
if err != nil {
|
|
t.Fatalf("error stating file[%s]: %v", name, err)
|
|
}
|
|
if int(finfo.Size()) != size {
|
|
t.Fatalf("invalid file[%s] expected[%d] got[%d]", name, size, finfo.Size())
|
|
}
|
|
}
|
|
|
|
func validateMeta(t *testing.T, desc string, f *File, startPos int64, endPos int64, dataSize int64, offset int64) {
|
|
if f.StartPos != startPos || f.EndPos != endPos || f.FileDataSize != dataSize || f.FileOffset != offset {
|
|
t.Fatalf("metadata error (%s): startpos[%d %d] endpos[%d %d] filedatasize[%d %d] fileoffset[%d %d]", desc, f.StartPos, startPos, f.EndPos, endPos, f.FileDataSize, dataSize, f.FileOffset, offset)
|
|
}
|
|
}
|
|
|
|
func dumpFile(name string) {
|
|
barr, _ := os.ReadFile(name)
|
|
str := string(barr)
|
|
str = strings.ReplaceAll(str, "\x00", ".")
|
|
fmt.Printf("%s<<<\n%s\n>>>\n", name, str)
|
|
}
|
|
|
|
func makeData(size int) string {
|
|
var rtn string
|
|
for {
|
|
if len(rtn) >= size {
|
|
break
|
|
}
|
|
needed := size - len(rtn)
|
|
if needed < 10 {
|
|
rtn += "123456789\n"[0:needed]
|
|
break
|
|
}
|
|
rtn += "123456789\n"
|
|
}
|
|
return rtn
|
|
}
|
|
|
|
func testFilePath(t *testing.T, name string) string {
|
|
tempDir := t.TempDir()
|
|
return filepath.Join(tempDir, name)
|
|
}
|
|
|
|
func createTestFile(t *testing.T, name string) (*File, string, error) {
|
|
fPath := testFilePath(t, name)
|
|
f, err := CreateCirFile(fPath, 100)
|
|
if err != nil {
|
|
return nil, fPath, err
|
|
}
|
|
return f, fPath, nil
|
|
}
|
|
|
|
func TestCreate(t *testing.T) {
|
|
const fName = "f1.cf"
|
|
fPath := testFilePath(t, fName)
|
|
f, err := OpenCirFile(fPath)
|
|
if err == nil || f != nil {
|
|
t.Fatalf("OpenCirFile %s should fail (no file)", fPath)
|
|
}
|
|
f, err = CreateCirFile(fPath, 100)
|
|
if err != nil {
|
|
t.Fatalf("CreateCirFile %s failed: %v", fPath, err)
|
|
}
|
|
if f == nil {
|
|
t.Fatalf("CreateCirFile %s returned nil", fPath)
|
|
}
|
|
err = f.ReadMeta(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("cannot readmeta from %s: %v", fPath, err)
|
|
}
|
|
validateFileSize(t, fPath, 256)
|
|
if f.Version != CurrentVersion || f.MaxSize != 100 || f.FileOffset != 0 || f.StartPos != FilePosEmpty || f.EndPos != 0 || f.FileDataSize != 0 || f.FlockStatus != 0 {
|
|
t.Fatalf("error with initial metadata #%v", f)
|
|
}
|
|
buf := make([]byte, 200)
|
|
realOffset, nr, err := f.ReadNext(context.Background(), buf, 0)
|
|
if realOffset != 0 || nr != 0 || err != nil {
|
|
t.Fatalf("error with empty read: real-offset[%d] nr[%d] err[%v]", realOffset, nr, err)
|
|
}
|
|
realOffset, nr, err = f.ReadNext(context.Background(), buf, 1000)
|
|
if realOffset != 0 || nr != 0 || err != nil {
|
|
t.Fatalf("error with empty read: real-offset[%d] nr[%d] err[%v]", realOffset, nr, err)
|
|
}
|
|
f2, err := CreateCirFile(fPath, 100)
|
|
if err == nil || f2 != nil {
|
|
t.Fatalf("should be an error to create duplicate CirFile")
|
|
}
|
|
}
|
|
|
|
const cannotAppendData = "cannot append data: %v"
|
|
const cannotReadNext = "cannot readnext: %v"
|
|
const cannotCreateCirFile = "cannot create cirfile [%s]: %v"
|
|
|
|
func TestFile(t *testing.T) {
|
|
const fName = "f1.cf"
|
|
f, fPath, err := createTestFile(t, fName)
|
|
if err != nil {
|
|
t.Fatalf(cannotCreateCirFile, fPath, err)
|
|
}
|
|
err = f.AppendData(context.Background(), nil)
|
|
if err != nil {
|
|
t.Fatalf(cannotAppendData, err)
|
|
}
|
|
validateFileSize(t, fPath, HeaderLen)
|
|
validateMeta(t, "1", f, FilePosEmpty, 0, 0, 0)
|
|
err = f.AppendData(context.Background(), []byte("hello"))
|
|
if err != nil {
|
|
t.Fatalf(cannotAppendData, err)
|
|
}
|
|
validateFileSize(t, fPath, HeaderLen+5)
|
|
validateMeta(t, "2", f, 0, 4, 5, 0)
|
|
err = f.AppendData(context.Background(), []byte(" foo"))
|
|
if err != nil {
|
|
t.Fatalf(cannotAppendData, err)
|
|
}
|
|
validateFileSize(t, fPath, HeaderLen+9)
|
|
validateMeta(t, "3", f, 0, 8, 9, 0)
|
|
err = f.AppendData(context.Background(), []byte("\n"+makeData(20)))
|
|
if err != nil {
|
|
t.Fatalf(cannotAppendData, err)
|
|
}
|
|
validateFileSize(t, fPath, HeaderLen+30)
|
|
validateMeta(t, "4", f, 0, 29, 30, 0)
|
|
|
|
data120 := makeData(120)
|
|
err = f.AppendData(context.Background(), []byte(data120))
|
|
if err != nil {
|
|
t.Fatalf(cannotAppendData, err)
|
|
}
|
|
validateFileSize(t, fPath, HeaderLen+100)
|
|
validateMeta(t, "5", f, 0, 99, 100, 50)
|
|
err = f.AppendData(context.Background(), []byte("foo "))
|
|
if err != nil {
|
|
t.Fatalf(cannotAppendData, err)
|
|
}
|
|
validateFileSize(t, fPath, HeaderLen+100)
|
|
validateMeta(t, "6", f, 4, 3, 100, 54)
|
|
|
|
buf := make([]byte, 5)
|
|
realOffset, nr, err := f.ReadNext(context.Background(), buf, 0)
|
|
if err != nil {
|
|
t.Fatalf(cannotReadNext, err)
|
|
}
|
|
if realOffset != 54 {
|
|
t.Fatalf("wrong realoffset got[%d] expected[%d]", realOffset, 54)
|
|
}
|
|
if nr != 5 {
|
|
t.Fatalf("wrong nr got[%d] expected[%d]", nr, 5)
|
|
}
|
|
if string(buf[0:nr]) != "56789" {
|
|
t.Fatalf("wrong buf return got[%s] expected[%s]", string(buf[0:nr]), "56789")
|
|
}
|
|
realOffset, nr, err = f.ReadNext(context.Background(), buf, 60)
|
|
if err != nil {
|
|
t.Fatalf(cannotReadNext, err)
|
|
}
|
|
if realOffset != 60 && nr != 5 {
|
|
t.Fatalf("invalid rtn realoffset[%d] nr[%d]", realOffset, nr)
|
|
}
|
|
if string(buf[0:nr]) != "12345" {
|
|
t.Fatalf("invalid rtn buf[%s]", string(buf[0:nr]))
|
|
}
|
|
realOffset, nr, err = f.ReadNext(context.Background(), buf, 800)
|
|
if err != nil || realOffset != 154 || nr != 0 {
|
|
t.Fatalf("invalid past end read: err[%v] realoffset[%d] nr[%d]", err, realOffset, nr)
|
|
}
|
|
realOffset, nr, err = f.ReadNext(context.Background(), buf, 150)
|
|
if err != nil || realOffset != 150 || nr != 4 || string(buf[0:nr]) != "foo " {
|
|
t.Fatalf("invalid end read: err[%v] realoffset[%d] nr[%d] buf[%s]", err, realOffset, nr, string(buf[0:nr]))
|
|
}
|
|
}
|
|
|
|
func TestFlock(t *testing.T) {
|
|
const fName = "f1.cf"
|
|
f, fPath, err := createTestFile(t, fName)
|
|
if err != nil {
|
|
t.Fatalf(cannotCreateCirFile, fPath, err)
|
|
}
|
|
fd2, err := os.OpenFile(fPath, os.O_RDWR, 0777)
|
|
if err != nil {
|
|
t.Fatalf("cannot open file: %v", err)
|
|
}
|
|
err = syscall.Flock(int(fd2.Fd()), syscall.LOCK_EX)
|
|
if err != nil {
|
|
t.Fatalf("cannot lock fd: %v", err)
|
|
}
|
|
err = f.AppendData(context.TODO(), []byte("hello"))
|
|
if err != syscall.EWOULDBLOCK {
|
|
t.Fatalf("append should fail with EWOULDBLOCK")
|
|
}
|
|
timeoutCtx, cancelFn := context.WithTimeout(context.Background(), 20*time.Millisecond)
|
|
defer cancelFn()
|
|
startTs := time.Now()
|
|
err = f.ReadMeta(timeoutCtx)
|
|
if err != context.DeadlineExceeded {
|
|
t.Fatalf("readmeta should fail with context.DeadlineExceeded")
|
|
}
|
|
dur := time.Since(startTs)
|
|
if dur < 20*time.Millisecond {
|
|
t.Fatalf("readmeta should take at least 20ms")
|
|
}
|
|
syscall.Flock(int(fd2.Fd()), syscall.LOCK_UN)
|
|
err = f.ReadMeta(timeoutCtx)
|
|
if err != nil {
|
|
t.Fatalf("readmeta err: %v", err)
|
|
}
|
|
err = syscall.Flock(int(fd2.Fd()), syscall.LOCK_SH)
|
|
if err != nil {
|
|
t.Fatalf("cannot flock: %v", err)
|
|
}
|
|
err = f.AppendData(context.TODO(), []byte("hello"))
|
|
if err != syscall.EWOULDBLOCK {
|
|
t.Fatalf("append should fail with EWOULDBLOCK")
|
|
}
|
|
err = f.ReadMeta(timeoutCtx)
|
|
if err != nil {
|
|
t.Fatalf("readmeta err (should work because LOCK_SH): %v", err)
|
|
}
|
|
fd2.Close()
|
|
err = f.AppendData(context.TODO(), []byte("hello"))
|
|
if err != nil {
|
|
t.Fatalf("append error (should work fd2 was closed): %v", err)
|
|
}
|
|
}
|
|
|
|
const writeAtError = "writeat error: %v"
|
|
|
|
func TestWriteAt(t *testing.T) {
|
|
const fName = "f1.cf"
|
|
f, fPath, err := createTestFile(t, fName)
|
|
if err != nil {
|
|
t.Fatalf("cannot create cirfile: %v", err)
|
|
}
|
|
err = f.WriteAt(context.TODO(), []byte("hello\nmike"), 4)
|
|
if err != nil {
|
|
t.Fatalf(writeAtError, err)
|
|
}
|
|
err = f.WriteAt(context.TODO(), []byte("t"), 2)
|
|
if err != nil {
|
|
t.Fatalf(writeAtError, err)
|
|
}
|
|
err = f.WriteAt(context.TODO(), []byte("more"), 30)
|
|
if err != nil {
|
|
t.Fatalf(writeAtError, err)
|
|
}
|
|
err = f.WriteAt(context.TODO(), []byte("\n"), 19)
|
|
if err != nil {
|
|
t.Fatalf(writeAtError, err)
|
|
}
|
|
dumpFile(fPath)
|
|
err = f.WriteAt(context.TODO(), []byte("hello"), 200)
|
|
if err != nil {
|
|
t.Fatalf(writeAtError, err)
|
|
}
|
|
buf := make([]byte, 10)
|
|
realOffset, nr, err := f.ReadNext(context.Background(), buf, 200)
|
|
if err != nil || realOffset != 200 || nr != 5 || string(buf[0:nr]) != "hello" {
|
|
t.Fatalf("invalid readnext: err[%v] realoffset[%d] nr[%d] buf[%s]", err, realOffset, nr, string(buf[0:nr]))
|
|
}
|
|
err = f.WriteAt(context.TODO(), []byte("0123456789\n"), 100)
|
|
if err != nil {
|
|
t.Fatalf(writeAtError, err)
|
|
}
|
|
dumpFile(fPath)
|
|
dataStr := makeData(200)
|
|
err = f.WriteAt(context.TODO(), []byte(dataStr), 50)
|
|
if err != nil {
|
|
t.Fatalf(writeAtError, err)
|
|
}
|
|
dumpFile(fPath)
|
|
|
|
dataStr = makeData(1000)
|
|
err = f.WriteAt(context.TODO(), []byte(dataStr), 1002)
|
|
if err != nil {
|
|
t.Fatalf(writeAtError, err)
|
|
}
|
|
err = f.WriteAt(context.TODO(), []byte("hello\n"), 2010)
|
|
if err != nil {
|
|
t.Fatalf(writeAtError, err)
|
|
}
|
|
err = f.AppendData(context.TODO(), []byte("foo\n"))
|
|
if err != nil {
|
|
t.Fatalf("appenddata error: %v", err)
|
|
}
|
|
dumpFile(fPath)
|
|
}
|