1// Copyright 2020 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package embedtest
6
7import (
8	"embed"
9	"io"
10	"reflect"
11	"testing"
12	"testing/fstest"
13)
14
15//go:embed testdata/h*.txt
16//go:embed c*.txt testdata/g*.txt
17var global embed.FS
18
19//go:embed c*txt
20var concurrency string
21
22//go:embed testdata/g*.txt
23var glass []byte
24
25func testFiles(t *testing.T, f embed.FS, name, data string) {
26	t.Helper()
27	d, err := f.ReadFile(name)
28	if err != nil {
29		t.Error(err)
30		return
31	}
32	if string(d) != data {
33		t.Errorf("read %v = %q, want %q", name, d, data)
34	}
35}
36
37func testString(t *testing.T, s, name, data string) {
38	t.Helper()
39	if s != data {
40		t.Errorf("%v = %q, want %q", name, s, data)
41	}
42}
43
44func testDir(t *testing.T, f embed.FS, name string, expect ...string) {
45	t.Helper()
46	dirs, err := f.ReadDir(name)
47	if err != nil {
48		t.Error(err)
49		return
50	}
51	var names []string
52	for _, d := range dirs {
53		name := d.Name()
54		if d.IsDir() {
55			name += "/"
56		}
57		names = append(names, name)
58	}
59	if !reflect.DeepEqual(names, expect) {
60		t.Errorf("readdir %v = %v, want %v", name, names, expect)
61	}
62}
63
64// Tests for issue 49514.
65var _ = '"'
66var _ = '\''
67var _ = '��'
68
69func TestGlobal(t *testing.T) {
70	testFiles(t, global, "concurrency.txt", "Concurrency is not parallelism.\n")
71	testFiles(t, global, "testdata/hello.txt", "hello, world\n")
72	testFiles(t, global, "testdata/glass.txt", "I can eat glass and it doesn't hurt me.\n")
73
74	if err := fstest.TestFS(global, "concurrency.txt", "testdata/hello.txt"); err != nil {
75		t.Fatal(err)
76	}
77
78	testString(t, concurrency, "concurrency", "Concurrency is not parallelism.\n")
79	testString(t, string(glass), "glass", "I can eat glass and it doesn't hurt me.\n")
80}
81
82//go:embed testdata
83var testDirAll embed.FS
84
85func TestDir(t *testing.T) {
86	all := testDirAll
87	testFiles(t, all, "testdata/hello.txt", "hello, world\n")
88	testFiles(t, all, "testdata/i/i18n.txt", "internationalization\n")
89	testFiles(t, all, "testdata/i/j/k/k8s.txt", "kubernetes\n")
90	testFiles(t, all, "testdata/ken.txt", "If a program is too slow, it must have a loop.\n")
91
92	testDir(t, all, ".", "testdata/")
93	testDir(t, all, "testdata/i", "i18n.txt", "j/")
94	testDir(t, all, "testdata/i/j", "k/")
95	testDir(t, all, "testdata/i/j/k", "k8s.txt")
96}
97
98var (
99	//go:embed testdata
100	testHiddenDir embed.FS
101
102	//go:embed testdata/*
103	testHiddenStar embed.FS
104)
105
106func TestHidden(t *testing.T) {
107	dir := testHiddenDir
108	star := testHiddenStar
109
110	t.Logf("//go:embed testdata")
111
112	testDir(t, dir, "testdata",
113		"-not-hidden/", "ascii.txt", "glass.txt", "hello.txt", "i/", "ken.txt")
114
115	t.Logf("//go:embed testdata/*")
116
117	testDir(t, star, "testdata",
118		"-not-hidden/", ".hidden/", "_hidden/", "ascii.txt", "glass.txt", "hello.txt", "i/", "ken.txt")
119
120	testDir(t, star, "testdata/.hidden",
121		"fortune.txt", "more/") // but not .more or _more
122}
123
124func TestUninitialized(t *testing.T) {
125	var uninitialized embed.FS
126	testDir(t, uninitialized, ".")
127	f, err := uninitialized.Open(".")
128	if err != nil {
129		t.Fatal(err)
130	}
131	defer f.Close()
132	fi, err := f.Stat()
133	if err != nil {
134		t.Fatal(err)
135	}
136	if !fi.IsDir() {
137		t.Errorf("in uninitialized embed.FS, . is not a directory")
138	}
139}
140
141var (
142	//go:embed "testdata/hello.txt"
143	helloT []T
144	//go:embed "testdata/hello.txt"
145	helloUint8 []uint8
146	//go:embed "testdata/hello.txt"
147	helloEUint8 []EmbedUint8
148	//go:embed "testdata/hello.txt"
149	helloBytes EmbedBytes
150	//go:embed "testdata/hello.txt"
151	helloString EmbedString
152)
153
154type T byte
155type EmbedUint8 uint8
156type EmbedBytes []byte
157type EmbedString string
158
159// golang.org/issue/47735
160func TestAliases(t *testing.T) {
161	all := testDirAll
162	want, e := all.ReadFile("testdata/hello.txt")
163	if e != nil {
164		t.Fatal("ReadFile:", e)
165	}
166	check := func(g any) {
167		got := reflect.ValueOf(g)
168		for i := 0; i < got.Len(); i++ {
169			if byte(got.Index(i).Uint()) != want[i] {
170				t.Fatalf("got %v want %v", got.Bytes(), want)
171			}
172		}
173	}
174	check(helloT)
175	check(helloUint8)
176	check(helloEUint8)
177	check(helloBytes)
178	check(helloString)
179}
180
181func TestOffset(t *testing.T) {
182	file, err := testDirAll.Open("testdata/hello.txt")
183	if err != nil {
184		t.Fatal("Open:", err)
185	}
186
187	want := "hello, world\n"
188
189	// Read the entire file.
190	got := make([]byte, len(want))
191	n, err := file.Read(got)
192	if err != nil {
193		t.Fatal("Read:", err)
194	}
195	if n != len(want) {
196		t.Fatal("Read:", n)
197	}
198	if string(got) != want {
199		t.Fatalf("Read: %q", got)
200	}
201
202	// Try to read one byte; confirm we're at the EOF.
203	var buf [1]byte
204	n, err = file.Read(buf[:])
205	if err != io.EOF {
206		t.Fatal("Read:", err)
207	}
208	if n != 0 {
209		t.Fatal("Read:", n)
210	}
211
212	// Use seek to get the offset at the EOF.
213	seeker := file.(io.Seeker)
214	off, err := seeker.Seek(0, io.SeekCurrent)
215	if err != nil {
216		t.Fatal("Seek:", err)
217	}
218	if off != int64(len(want)) {
219		t.Fatal("Seek:", off)
220	}
221
222	// Use ReadAt to read the entire file, ignoring the offset.
223	at := file.(io.ReaderAt)
224	got = make([]byte, len(want))
225	n, err = at.ReadAt(got, 0)
226	if err != nil {
227		t.Fatal("ReadAt:", err)
228	}
229	if n != len(want) {
230		t.Fatalf("ReadAt: got %d bytes, want %d bytes", n, len(want))
231	}
232	if string(got) != want {
233		t.Fatalf("ReadAt: got %q, want %q", got, want)
234	}
235
236	// Use ReadAt with non-zero offset.
237	off = int64(7)
238	want = want[off:]
239	got = make([]byte, len(want))
240	n, err = at.ReadAt(got, off)
241	if err != nil {
242		t.Fatal("ReadAt:", err)
243	}
244	if n != len(want) {
245		t.Fatalf("ReadAt: got %d bytes, want %d bytes", n, len(want))
246	}
247	if string(got) != want {
248		t.Fatalf("ReadAt: got %q, want %q", got, want)
249	}
250}
251