Skip to content

Commit 5f19042

Browse files
committed
internal/registrytest: rewrite in terms of io/fs
This way, we can use it in the context of a testscript in cmd/cue; testscript.Setup doesn't give us direct access to the txtar.Archive, but it does extract it to the working directory, so we can use os.DirFS. Signed-off-by: Daniel Martí <[email protected]> Change-Id: I04fe783d6d16e802c1bd62f413877055bd11655b Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1169976 Reviewed-by: Roger Peppe <[email protected]> Unity-Result: CUE porcuepine <[email protected]> TryBot-Result: CUEcueckoo <[email protected]>
1 parent 633874c commit 5f19042

File tree

4 files changed

+237
-12
lines changed

4 files changed

+237
-12
lines changed

cue/load/module_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func TestModuleFetch(t *testing.T) {
1616
Name: "modfetch",
1717
}
1818
test.Run(t, func(t *cuetxtar.Test) {
19-
r, err := registrytest.New(t.Archive)
19+
r, err := registrytest.New(registrytest.TxtarFS(t.Archive))
2020
if err != nil {
2121
t.Fatal(err)
2222
}

internal/registrytest/registry.go

+26-10
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package registrytest
33
import (
44
"bytes"
55
"context"
6+
"errors"
67
"fmt"
78
"io"
9+
"io/fs"
810
"net/http/httptest"
911
"strings"
1012

@@ -28,13 +30,13 @@ import (
2830
// contain a cue.mod/module.cue file holding the module info.
2931
//
3032
// The Registry should be closed after use.
31-
func New(ar *txtar.Archive) (*Registry, error) {
33+
func New(fsys fs.FS) (*Registry, error) {
3234
srv := httptest.NewServer(ociserver.New(ocimem.New(), nil))
3335
client, err := modregistry.NewClient(srv.URL, "cue/")
3436
if err != nil {
3537
return nil, fmt.Errorf("cannot make client: %v", err)
3638
}
37-
mods, err := getModules(ar)
39+
mods, err := getModules(fsys)
3840
if err != nil {
3941
return nil, fmt.Errorf("invalid modules: %v", err)
4042
}
@@ -100,27 +102,41 @@ type handler struct {
100102
modules []*moduleContent
101103
}
102104

103-
func getModules(ar *txtar.Archive) (map[module.Version]*moduleContent, error) {
105+
func getModules(fsys fs.FS) (map[module.Version]*moduleContent, error) {
104106
ctx := cuecontext.New()
105107
modules := make(map[string]*moduleContent)
106-
for _, f := range ar.Files {
107-
path := strings.TrimPrefix(f.Name, "_registry/")
108-
if len(path) == len(f.Name) {
109-
continue
108+
if err := fs.WalkDir(fsys, "_registry", func(path string, d fs.DirEntry, err error) error {
109+
if err != nil {
110+
// If a filesystem has no _registry directory at all,
111+
// return zero modules without an error.
112+
if path == "_registry" && errors.Is(err, fs.ErrNotExist) {
113+
return fs.SkipAll
114+
}
115+
return err
110116
}
111-
modver, rest, ok := strings.Cut(path, "/")
117+
if d.IsDir() {
118+
return nil // we're only interested in regular files, not their parent directories
119+
}
120+
modver, rest, ok := strings.Cut(strings.TrimPrefix(path, "_registry/"), "/")
112121
if !ok {
113-
return nil, fmt.Errorf("_registry should only contain directories, but found regular file %q", path)
122+
return fmt.Errorf("_registry should only contain directories, but found regular file %q", path)
114123
}
115124
content := modules[modver]
116125
if content == nil {
117126
content = &moduleContent{}
118127
modules[modver] = content
119128
}
129+
data, err := fs.ReadFile(fsys, path)
130+
if err != nil {
131+
return err
132+
}
120133
content.files = append(content.files, txtar.File{
121134
Name: rest,
122-
Data: f.Data,
135+
Data: data,
123136
})
137+
return nil
138+
}); err != nil {
139+
return nil, err
124140
}
125141
for modver, content := range modules {
126142
if err := content.init(ctx, modver); err != nil {

internal/registrytest/registry_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func TestRegistry(t *testing.T) {
3232
t.Fatal(err)
3333
}
3434
t.Run(strings.TrimSuffix(name, ".txtar"), func(t *testing.T) {
35-
r, err := New(ar)
35+
r, err := New(TxtarFS(ar))
3636
if err != nil {
3737
t.Fatal(err)
3838
}

internal/registrytest/txtar_fs.go

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// Borrowed from https://github.com/golang/tools/pull/289.
2+
// TODO(mvdan): replace by x/tools/txtar once the proposal at
3+
// https://github.com/golang/go/issues/44158 is accepted and implemented.
4+
5+
// Copyright 2022 The Go Authors. All rights reserved.
6+
// Use of this source code is governed by a BSD-style
7+
// license that can be found in the LICENSE file.
8+
9+
package registrytest
10+
11+
import (
12+
"bytes"
13+
"errors"
14+
"io"
15+
"io/fs"
16+
"path"
17+
"strings"
18+
"time"
19+
20+
"golang.org/x/tools/txtar"
21+
)
22+
23+
// TxtarFS returns an fs.FS which reads from a txtar.Archive.
24+
func TxtarFS(ar *txtar.Archive) fs.FS {
25+
return archiveFS{ar}
26+
}
27+
28+
type archiveFS struct {
29+
a *txtar.Archive
30+
}
31+
32+
// Open implements fs.FS.
33+
func (fsys archiveFS) Open(name string) (fs.File, error) {
34+
if !fs.ValidPath(name) {
35+
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
36+
}
37+
38+
for _, f := range fsys.a.Files {
39+
// In case the txtar has weird filenames
40+
cleanName := path.Clean(f.Name)
41+
if name == cleanName {
42+
return newOpenFile(f), nil
43+
}
44+
}
45+
var entries []fileInfo
46+
dirs := make(map[string]bool)
47+
prefix := name + "/"
48+
if name == "." {
49+
prefix = ""
50+
}
51+
52+
for _, f := range fsys.a.Files {
53+
cleanName := path.Clean(f.Name)
54+
if !strings.HasPrefix(cleanName, prefix) {
55+
continue
56+
}
57+
felem := cleanName[len(prefix):]
58+
i := strings.Index(felem, "/")
59+
if i < 0 {
60+
entries = append(entries, newFileInfo(f))
61+
} else {
62+
dirs[felem[:i]] = true
63+
}
64+
}
65+
// If there are no children of the name,
66+
// then the directory is treated as not existing
67+
// unless the directory is "."
68+
if len(entries) == 0 && len(dirs) == 0 && name != "." {
69+
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
70+
}
71+
72+
for name := range dirs {
73+
entries = append(entries, newDirInfo(name))
74+
}
75+
76+
return &openDir{newDirInfo(name), entries, 0}, nil
77+
}
78+
79+
var _ fs.ReadFileFS = archiveFS{}
80+
81+
// ReadFile implements fs.ReadFileFS.
82+
func (fsys archiveFS) ReadFile(name string) ([]byte, error) {
83+
if !fs.ValidPath(name) {
84+
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
85+
}
86+
if name == "." {
87+
return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("is a directory")}
88+
}
89+
prefix := name + "/"
90+
for _, f := range fsys.a.Files {
91+
if cleanName := path.Clean(f.Name); name == cleanName {
92+
return append(([]byte)(nil), f.Data...), nil
93+
}
94+
// It's a directory
95+
if strings.HasPrefix(f.Name, prefix) {
96+
return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("is a directory")}
97+
}
98+
}
99+
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
100+
}
101+
102+
var (
103+
_ fs.File = (*openFile)(nil)
104+
_ io.ReadSeekCloser = (*openFile)(nil)
105+
_ io.ReaderAt = (*openFile)(nil)
106+
_ io.WriterTo = (*openFile)(nil)
107+
)
108+
109+
type openFile struct {
110+
bytes.Reader
111+
fi fileInfo
112+
}
113+
114+
func newOpenFile(f txtar.File) *openFile {
115+
var o openFile
116+
o.Reader.Reset(f.Data)
117+
o.fi = fileInfo{f, 0444}
118+
return &o
119+
}
120+
121+
func (o *openFile) Stat() (fs.FileInfo, error) { return o.fi, nil }
122+
123+
func (o *openFile) Close() error { return nil }
124+
125+
var _ fs.FileInfo = fileInfo{}
126+
127+
type fileInfo struct {
128+
f txtar.File
129+
m fs.FileMode
130+
}
131+
132+
func newFileInfo(f txtar.File) fileInfo {
133+
return fileInfo{f, 0444}
134+
}
135+
136+
func newDirInfo(name string) fileInfo {
137+
return fileInfo{txtar.File{Name: name}, fs.ModeDir | 0555}
138+
}
139+
140+
func (f fileInfo) Name() string { return path.Base(f.f.Name) }
141+
func (f fileInfo) Size() int64 { return int64(len(f.f.Data)) }
142+
func (f fileInfo) Mode() fs.FileMode { return f.m }
143+
func (f fileInfo) Type() fs.FileMode { return f.m.Type() }
144+
func (f fileInfo) ModTime() time.Time { return time.Time{} }
145+
func (f fileInfo) IsDir() bool { return f.m.IsDir() }
146+
func (f fileInfo) Sys() interface{} { return f.f }
147+
func (f fileInfo) Info() (fs.FileInfo, error) { return f, nil }
148+
149+
type openDir struct {
150+
dirInfo fileInfo
151+
entries []fileInfo
152+
offset int
153+
}
154+
155+
func (d *openDir) Stat() (fs.FileInfo, error) { return &d.dirInfo, nil }
156+
func (d *openDir) Close() error { return nil }
157+
func (d *openDir) Read(b []byte) (int, error) {
158+
return 0, &fs.PathError{Op: "read", Path: d.dirInfo.f.Name, Err: errors.New("is a directory")}
159+
}
160+
161+
func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {
162+
n := len(d.entries) - d.offset
163+
if count > 0 && n > count {
164+
n = count
165+
}
166+
if n == 0 && count > 0 {
167+
return nil, io.EOF
168+
}
169+
entries := make([]fs.DirEntry, n)
170+
for i := range entries {
171+
entries[i] = &d.entries[d.offset+i]
172+
}
173+
d.offset += n
174+
return entries, nil
175+
}
176+
177+
// From constructs an Archive with the contents of fsys and an empty Comment.
178+
// Subsequent changes to fsys are not reflected in the returned archive.
179+
//
180+
// The transformation is lossy.
181+
// For example, because directories are implicit in txtar archives,
182+
// empty directories in fsys will be lost,
183+
// and txtar does not represent file mode, mtime, or other file metadata.
184+
// From does not guarantee that a.File[i].Data contains no file marker lines.
185+
// See also warnings on Format.
186+
// In short, it is unwise to use txtar as a generic filesystem serialization mechanism.
187+
func From(fsys fs.FS) (*txtar.Archive, error) {
188+
ar := new(txtar.Archive)
189+
walkfn := func(path string, d fs.DirEntry, err error) error {
190+
if err != nil {
191+
return err
192+
}
193+
if d.IsDir() {
194+
// Directories in txtar are implicit.
195+
return nil
196+
}
197+
data, err := fs.ReadFile(fsys, path)
198+
if err != nil {
199+
return err
200+
}
201+
ar.Files = append(ar.Files, txtar.File{Name: path, Data: data})
202+
return nil
203+
}
204+
205+
if err := fs.WalkDir(fsys, ".", walkfn); err != nil {
206+
return nil, err
207+
}
208+
return ar, nil
209+
}

0 commit comments

Comments
 (0)