package files import ( "context" "io" "os" "path/filepath" "testing" ) import "strings" type stringsBuilder = strings.Builder type countingFS struct { Local opens []int64 } func (c *countingFS) OpenAt(ctx context.Context, path string, off int64) (io.ReadCloser, error) { c.opens = append(c.opens, off) return c.Local.OpenAt(ctx, path, off) } // ServeContent's moves — size from the end, seek to the range, read forward — open the // backend once, at the range; a seek to where the stream already is costs nothing. func TestSeeker(t *testing.T) { p := filepath.Join(t.TempDir(), "f") data := make([]byte, 1000) for i := range data { data[i] = byte(i) } if err := os.WriteFile(p, data, 0o644); err != nil { t.Fatal(err) } fs := &countingFS{} s := NewSeeker(context.Background(), fs, p, int64(len(data))) if n, _ := s.Seek(0, io.SeekEnd); n != 1000 { t.Fatalf("end = %d", n) } if _, err := s.Seek(100, io.SeekStart); err != nil { t.Fatal(err) } buf := make([]byte, 50) if n, err := io.ReadFull(s, buf); err != nil || n != 50 || buf[0] != 100 || buf[49] != 149 { t.Fatalf("read at 100: %d %v %v", n, err, buf[:2]) } if _, err := s.Seek(150, io.SeekStart); err != nil { // where it already is t.Fatal(err) } if n, _ := io.ReadFull(s, buf); n != 50 || buf[0] != 150 { t.Fatal("read after a no-op seek") } if len(fs.opens) != 1 || fs.opens[0] != 100 { t.Fatalf("opens = %v, want [100]", fs.opens) } if _, err := s.Seek(0, io.SeekStart); err != nil { t.Fatal(err) } rest, err := io.ReadAll(s) if err != nil || len(rest) != 1000 || rest[999] != data[999] { t.Fatalf("read all from the start: %d %v", len(rest), err) } if len(fs.opens) != 2 || fs.opens[1] != 0 { t.Fatalf("opens = %v, want [100 0]", fs.opens) } if n, err := s.Read(buf); n != 0 || err != io.EOF { t.Fatal("past the end is EOF") } if _, err := s.Seek(-1, io.SeekStart); err == nil { t.Fatal("before the start") } if err := s.Close(); err != nil { t.Fatal(err) } // Check opens at the start and keeps that stream for a read from the start s2 := NewSeeker(context.Background(), fs, p, 1000) if err := s2.Check(); err != nil { t.Fatal(err) } s2.Seek(0, io.SeekEnd) s2.Seek(0, io.SeekStart) if n, _ := io.ReadFull(s2, buf); n != 50 || buf[0] != 0 { t.Fatal("read from the start after Check") } if len(fs.opens) != 3 { t.Fatalf("Check's open was not reused: %v", fs.opens) } s2.Close() if err := NewSeeker(context.Background(), fs, filepath.Join(t.TempDir(), "nope"), 5).Check(); err == nil { t.Fatal("Check must report a file that can't be opened") } } // The helper's read takes an offset, for the Sudo backend. func TestHelperReadOffset(t *testing.T) { p := filepath.Join(t.TempDir(), "f") if err := os.WriteFile(p, []byte("0123456789"), 0o644); err != nil { t.Fatal(err) } var out, errb stringsBuilder if rc := Helper([]string{"read", "--offset", "7", p}, nil, &out, &errb); rc != 0 { t.Fatalf("rc %d: %s", rc, errb.String()) } if out.String() != "789" { t.Fatalf("got %q", out.String()) } out.Reset() if rc := Helper([]string{"read", p}, nil, &out, &errb); rc != 0 || out.String() != "0123456789" { t.Fatalf("plain read: rc %d %q", rc, out.String()) } }