summaryrefslogtreecommitdiffstats
path: root/src/embed
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/embed/embed.go432
-rw-r--r--src/embed/example_test.go23
-rw-r--r--src/embed/internal/embedtest/concurrency.txt1
-rw-r--r--src/embed/internal/embedtest/embed_test.go178
-rw-r--r--src/embed/internal/embedtest/embedx_test.go92
-rw-r--r--src/embed/internal/embedtest/testdata/-not-hidden/fortune.txt2
-rw-r--r--src/embed/internal/embedtest/testdata/.hidden/.more/tip.txt1
-rw-r--r--src/embed/internal/embedtest/testdata/.hidden/_more/tip.txt1
-rw-r--r--src/embed/internal/embedtest/testdata/.hidden/fortune.txt2
-rw-r--r--src/embed/internal/embedtest/testdata/.hidden/more/tip.txt1
-rw-r--r--src/embed/internal/embedtest/testdata/_hidden/fortune.txt2
-rw-r--r--src/embed/internal/embedtest/testdata/ascii.txt25
-rw-r--r--src/embed/internal/embedtest/testdata/glass.txt1
-rw-r--r--src/embed/internal/embedtest/testdata/hello.txt1
-rw-r--r--src/embed/internal/embedtest/testdata/i/i18n.txt1
-rw-r--r--src/embed/internal/embedtest/testdata/i/j/k/k8s.txt1
-rw-r--r--src/embed/internal/embedtest/testdata/ken.txt1
17 files changed, 765 insertions, 0 deletions
diff --git a/src/embed/embed.go b/src/embed/embed.go
new file mode 100644
index 0000000..c54b961
--- /dev/null
+++ b/src/embed/embed.go
@@ -0,0 +1,432 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package embed provides access to files embedded in the running Go program.
+//
+// Go source files that import "embed" can use the //go:embed directive
+// to initialize a variable of type string, []byte, or FS with the contents of
+// files read from the package directory or subdirectories at compile time.
+//
+// For example, here are three ways to embed a file named hello.txt
+// and then print its contents at run time.
+//
+// Embedding one file into a string:
+//
+// import _ "embed"
+//
+// //go:embed hello.txt
+// var s string
+// print(s)
+//
+// Embedding one file into a slice of bytes:
+//
+// import _ "embed"
+//
+// //go:embed hello.txt
+// var b []byte
+// print(string(b))
+//
+// Embedded one or more files into a file system:
+//
+// import "embed"
+//
+// //go:embed hello.txt
+// var f embed.FS
+// data, _ := f.ReadFile("hello.txt")
+// print(string(data))
+//
+// # Directives
+//
+// A //go:embed directive above a variable declaration specifies which files to embed,
+// using one or more path.Match patterns.
+//
+// The directive must immediately precede a line containing the declaration of a single variable.
+// Only blank lines and ‘//’ line comments are permitted between the directive and the declaration.
+//
+// The type of the variable must be a string type, or a slice of a byte type,
+// or FS (or an alias of FS).
+//
+// For example:
+//
+// package server
+//
+// import "embed"
+//
+// // content holds our static web server content.
+// //go:embed image/* template/*
+// //go:embed html/index.html
+// var content embed.FS
+//
+// The Go build system will recognize the directives and arrange for the declared variable
+// (in the example above, content) to be populated with the matching files from the file system.
+//
+// The //go:embed directive accepts multiple space-separated patterns for
+// brevity, but it can also be repeated, to avoid very long lines when there are
+// many patterns. The patterns are interpreted relative to the package directory
+// containing the source file. The path separator is a forward slash, even on
+// Windows systems. Patterns may not contain ‘.’ or ‘..’ or empty path elements,
+// nor may they begin or end with a slash. To match everything in the current
+// directory, use ‘*’ instead of ‘.’. To allow for naming files with spaces in
+// their names, patterns can be written as Go double-quoted or back-quoted
+// string literals.
+//
+// If a pattern names a directory, all files in the subtree rooted at that directory are
+// embedded (recursively), except that files with names beginning with ‘.’ or ‘_’
+// are excluded. So the variable in the above example is almost equivalent to:
+//
+// // content is our static web server content.
+// //go:embed image template html/index.html
+// var content embed.FS
+//
+// The difference is that ‘image/*’ embeds ‘image/.tempfile’ while ‘image’ does not.
+// Neither embeds ‘image/dir/.tempfile’.
+//
+// If a pattern begins with the prefix ‘all:’, then the rule for walking directories is changed
+// to include those files beginning with ‘.’ or ‘_’. For example, ‘all:image’ embeds
+// both ‘image/.tempfile’ and ‘image/dir/.tempfile’.
+//
+// The //go:embed directive can be used with both exported and unexported variables,
+// depending on whether the package wants to make the data available to other packages.
+// It can only be used with variables at package scope, not with local variables.
+//
+// Patterns must not match files outside the package's module, such as ‘.git/*’ or symbolic links.
+// Patterns must not match files whose names include the special punctuation characters " * < > ? ` ' | / \ and :.
+// Matches for empty directories are ignored. After that, each pattern in a //go:embed line
+// must match at least one file or non-empty directory.
+//
+// If any patterns are invalid or have invalid matches, the build will fail.
+//
+// # Strings and Bytes
+//
+// The //go:embed line for a variable of type string or []byte can have only a single pattern,
+// and that pattern can match only a single file. The string or []byte is initialized with
+// the contents of that file.
+//
+// The //go:embed directive requires importing "embed", even when using a string or []byte.
+// In source files that don't refer to embed.FS, use a blank import (import _ "embed").
+//
+// # File Systems
+//
+// For embedding a single file, a variable of type string or []byte is often best.
+// The FS type enables embedding a tree of files, such as a directory of static
+// web server content, as in the example above.
+//
+// FS implements the io/fs package's FS interface, so it can be used with any package that
+// understands file systems, including net/http, text/template, and html/template.
+//
+// For example, given the content variable in the example above, we can write:
+//
+// http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(content))))
+//
+// template.ParseFS(content, "*.tmpl")
+//
+// # Tools
+//
+// To support tools that analyze Go packages, the patterns found in //go:embed lines
+// are available in “go list” output. See the EmbedPatterns, TestEmbedPatterns,
+// and XTestEmbedPatterns fields in the “go help list” output.
+package embed
+
+import (
+ "errors"
+ "io"
+ "io/fs"
+ "time"
+)
+
+// An FS is a read-only collection of files, usually initialized with a //go:embed directive.
+// When declared without a //go:embed directive, an FS is an empty file system.
+//
+// An FS is a read-only value, so it is safe to use from multiple goroutines
+// simultaneously and also safe to assign values of type FS to each other.
+//
+// FS implements fs.FS, so it can be used with any package that understands
+// file system interfaces, including net/http, text/template, and html/template.
+//
+// See the package documentation for more details about initializing an FS.
+type FS struct {
+ // The compiler knows the layout of this struct.
+ // See cmd/compile/internal/staticdata's WriteEmbed.
+ //
+ // The files list is sorted by name but not by simple string comparison.
+ // Instead, each file's name takes the form "dir/elem" or "dir/elem/".
+ // The optional trailing slash indicates that the file is itself a directory.
+ // The files list is sorted first by dir (if dir is missing, it is taken to be ".")
+ // and then by base, so this list of files:
+ //
+ // p
+ // q/
+ // q/r
+ // q/s/
+ // q/s/t
+ // q/s/u
+ // q/v
+ // w
+ //
+ // is actually sorted as:
+ //
+ // p # dir=. elem=p
+ // q/ # dir=. elem=q
+ // w/ # dir=. elem=w
+ // q/r # dir=q elem=r
+ // q/s/ # dir=q elem=s
+ // q/v # dir=q elem=v
+ // q/s/t # dir=q/s elem=t
+ // q/s/u # dir=q/s elem=u
+ //
+ // This order brings directory contents together in contiguous sections
+ // of the list, allowing a directory read to use binary search to find
+ // the relevant sequence of entries.
+ files *[]file
+}
+
+// split splits the name into dir and elem as described in the
+// comment in the FS struct above. isDir reports whether the
+// final trailing slash was present, indicating that name is a directory.
+func split(name string) (dir, elem string, isDir bool) {
+ if name[len(name)-1] == '/' {
+ isDir = true
+ name = name[:len(name)-1]
+ }
+ i := len(name) - 1
+ for i >= 0 && name[i] != '/' {
+ i--
+ }
+ if i < 0 {
+ return ".", name, isDir
+ }
+ return name[:i], name[i+1:], isDir
+}
+
+// trimSlash trims a trailing slash from name, if present,
+// returning the possibly shortened name.
+func trimSlash(name string) string {
+ if len(name) > 0 && name[len(name)-1] == '/' {
+ return name[:len(name)-1]
+ }
+ return name
+}
+
+var (
+ _ fs.ReadDirFS = FS{}
+ _ fs.ReadFileFS = FS{}
+)
+
+// A file is a single file in the FS.
+// It implements fs.FileInfo and fs.DirEntry.
+type file struct {
+ // The compiler knows the layout of this struct.
+ // See cmd/compile/internal/staticdata's WriteEmbed.
+ name string
+ data string
+ hash [16]byte // truncated SHA256 hash
+}
+
+var (
+ _ fs.FileInfo = (*file)(nil)
+ _ fs.DirEntry = (*file)(nil)
+)
+
+func (f *file) Name() string { _, elem, _ := split(f.name); return elem }
+func (f *file) Size() int64 { return int64(len(f.data)) }
+func (f *file) ModTime() time.Time { return time.Time{} }
+func (f *file) IsDir() bool { _, _, isDir := split(f.name); return isDir }
+func (f *file) Sys() any { return nil }
+func (f *file) Type() fs.FileMode { return f.Mode().Type() }
+func (f *file) Info() (fs.FileInfo, error) { return f, nil }
+
+func (f *file) Mode() fs.FileMode {
+ if f.IsDir() {
+ return fs.ModeDir | 0555
+ }
+ return 0444
+}
+
+// dotFile is a file for the root directory,
+// which is omitted from the files list in a FS.
+var dotFile = &file{name: "./"}
+
+// lookup returns the named file, or nil if it is not present.
+func (f FS) lookup(name string) *file {
+ if !fs.ValidPath(name) {
+ // The compiler should never emit a file with an invalid name,
+ // so this check is not strictly necessary (if name is invalid,
+ // we shouldn't find a match below), but it's a good backstop anyway.
+ return nil
+ }
+ if name == "." {
+ return dotFile
+ }
+ if f.files == nil {
+ return nil
+ }
+
+ // Binary search to find where name would be in the list,
+ // and then check if name is at that position.
+ dir, elem, _ := split(name)
+ files := *f.files
+ i := sortSearch(len(files), func(i int) bool {
+ idir, ielem, _ := split(files[i].name)
+ return idir > dir || idir == dir && ielem >= elem
+ })
+ if i < len(files) && trimSlash(files[i].name) == name {
+ return &files[i]
+ }
+ return nil
+}
+
+// readDir returns the list of files corresponding to the directory dir.
+func (f FS) readDir(dir string) []file {
+ if f.files == nil {
+ return nil
+ }
+ // Binary search to find where dir starts and ends in the list
+ // and then return that slice of the list.
+ files := *f.files
+ i := sortSearch(len(files), func(i int) bool {
+ idir, _, _ := split(files[i].name)
+ return idir >= dir
+ })
+ j := sortSearch(len(files), func(j int) bool {
+ jdir, _, _ := split(files[j].name)
+ return jdir > dir
+ })
+ return files[i:j]
+}
+
+// Open opens the named file for reading and returns it as an fs.File.
+//
+// The returned file implements io.Seeker when the file is not a directory.
+func (f FS) Open(name string) (fs.File, error) {
+ file := f.lookup(name)
+ if file == nil {
+ return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
+ }
+ if file.IsDir() {
+ return &openDir{file, f.readDir(name), 0}, nil
+ }
+ return &openFile{file, 0}, nil
+}
+
+// ReadDir reads and returns the entire named directory.
+func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
+ file, err := f.Open(name)
+ if err != nil {
+ return nil, err
+ }
+ dir, ok := file.(*openDir)
+ if !ok {
+ return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("not a directory")}
+ }
+ list := make([]fs.DirEntry, len(dir.files))
+ for i := range list {
+ list[i] = &dir.files[i]
+ }
+ return list, nil
+}
+
+// ReadFile reads and returns the content of the named file.
+func (f FS) ReadFile(name string) ([]byte, error) {
+ file, err := f.Open(name)
+ if err != nil {
+ return nil, err
+ }
+ ofile, ok := file.(*openFile)
+ if !ok {
+ return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("is a directory")}
+ }
+ return []byte(ofile.f.data), nil
+}
+
+// An openFile is a regular file open for reading.
+type openFile struct {
+ f *file // the file itself
+ offset int64 // current read offset
+}
+
+var (
+ _ io.Seeker = (*openFile)(nil)
+)
+
+func (f *openFile) Close() error { return nil }
+func (f *openFile) Stat() (fs.FileInfo, error) { return f.f, nil }
+
+func (f *openFile) Read(b []byte) (int, error) {
+ if f.offset >= int64(len(f.f.data)) {
+ return 0, io.EOF
+ }
+ if f.offset < 0 {
+ return 0, &fs.PathError{Op: "read", Path: f.f.name, Err: fs.ErrInvalid}
+ }
+ n := copy(b, f.f.data[f.offset:])
+ f.offset += int64(n)
+ return n, nil
+}
+
+func (f *openFile) Seek(offset int64, whence int) (int64, error) {
+ switch whence {
+ case 0:
+ // offset += 0
+ case 1:
+ offset += f.offset
+ case 2:
+ offset += int64(len(f.f.data))
+ }
+ if offset < 0 || offset > int64(len(f.f.data)) {
+ return 0, &fs.PathError{Op: "seek", Path: f.f.name, Err: fs.ErrInvalid}
+ }
+ f.offset = offset
+ return offset, nil
+}
+
+// An openDir is a directory open for reading.
+type openDir struct {
+ f *file // the directory file itself
+ files []file // the directory contents
+ offset int // the read offset, an index into the files slice
+}
+
+func (d *openDir) Close() error { return nil }
+func (d *openDir) Stat() (fs.FileInfo, error) { return d.f, nil }
+
+func (d *openDir) Read([]byte) (int, error) {
+ return 0, &fs.PathError{Op: "read", Path: d.f.name, Err: errors.New("is a directory")}
+}
+
+func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {
+ n := len(d.files) - d.offset
+ if n == 0 {
+ if count <= 0 {
+ return nil, nil
+ }
+ return nil, io.EOF
+ }
+ if count > 0 && n > count {
+ n = count
+ }
+ list := make([]fs.DirEntry, n)
+ for i := range list {
+ list[i] = &d.files[d.offset+i]
+ }
+ d.offset += n
+ return list, nil
+}
+
+// sortSearch is like sort.Search, avoiding an import.
+func sortSearch(n int, f func(int) bool) int {
+ // Define f(-1) == false and f(n) == true.
+ // Invariant: f(i-1) == false, f(j) == true.
+ i, j := 0, n
+ for i < j {
+ h := int(uint(i+j) >> 1) // avoid overflow when computing h
+ // i ≤ h < j
+ if !f(h) {
+ i = h + 1 // preserves f(i-1) == false
+ } else {
+ j = h // preserves f(j) == true
+ }
+ }
+ // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i.
+ return i
+}
diff --git a/src/embed/example_test.go b/src/embed/example_test.go
new file mode 100644
index 0000000..5498c27
--- /dev/null
+++ b/src/embed/example_test.go
@@ -0,0 +1,23 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package embed_test
+
+import (
+ "embed"
+ "log"
+ "net/http"
+)
+
+//go:embed internal/embedtest/testdata/*.txt
+var content embed.FS
+
+func Example() {
+ mutex := http.NewServeMux()
+ mutex.Handle("/", http.FileServer(http.FS(content)))
+ err := http.ListenAndServe(":8080", mutex)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/src/embed/internal/embedtest/concurrency.txt b/src/embed/internal/embedtest/concurrency.txt
new file mode 100644
index 0000000..0814741
--- /dev/null
+++ b/src/embed/internal/embedtest/concurrency.txt
@@ -0,0 +1 @@
+Concurrency is not parallelism.
diff --git a/src/embed/internal/embedtest/embed_test.go b/src/embed/internal/embedtest/embed_test.go
new file mode 100644
index 0000000..cbd58ee
--- /dev/null
+++ b/src/embed/internal/embedtest/embed_test.go
@@ -0,0 +1,178 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package embedtest
+
+import (
+ "embed"
+ "reflect"
+ "testing"
+ "testing/fstest"
+)
+
+//go:embed testdata/h*.txt
+//go:embed c*.txt testdata/g*.txt
+var global embed.FS
+
+//go:embed c*txt
+var concurrency string
+
+//go:embed testdata/g*.txt
+var glass []byte
+
+func testFiles(t *testing.T, f embed.FS, name, data string) {
+ t.Helper()
+ d, err := f.ReadFile(name)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if string(d) != data {
+ t.Errorf("read %v = %q, want %q", name, d, data)
+ }
+}
+
+func testString(t *testing.T, s, name, data string) {
+ t.Helper()
+ if s != data {
+ t.Errorf("%v = %q, want %q", name, s, data)
+ }
+}
+
+func testDir(t *testing.T, f embed.FS, name string, expect ...string) {
+ t.Helper()
+ dirs, err := f.ReadDir(name)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ var names []string
+ for _, d := range dirs {
+ name := d.Name()
+ if d.IsDir() {
+ name += "/"
+ }
+ names = append(names, name)
+ }
+ if !reflect.DeepEqual(names, expect) {
+ t.Errorf("readdir %v = %v, want %v", name, names, expect)
+ }
+}
+
+// Tests for issue 49514.
+var _ = '"'
+var _ = '\''
+var _ = '🦆'
+
+func TestGlobal(t *testing.T) {
+ testFiles(t, global, "concurrency.txt", "Concurrency is not parallelism.\n")
+ testFiles(t, global, "testdata/hello.txt", "hello, world\n")
+ testFiles(t, global, "testdata/glass.txt", "I can eat glass and it doesn't hurt me.\n")
+
+ if err := fstest.TestFS(global, "concurrency.txt", "testdata/hello.txt"); err != nil {
+ t.Fatal(err)
+ }
+
+ testString(t, concurrency, "concurrency", "Concurrency is not parallelism.\n")
+ testString(t, string(glass), "glass", "I can eat glass and it doesn't hurt me.\n")
+}
+
+//go:embed testdata
+var testDirAll embed.FS
+
+func TestDir(t *testing.T) {
+ all := testDirAll
+ testFiles(t, all, "testdata/hello.txt", "hello, world\n")
+ testFiles(t, all, "testdata/i/i18n.txt", "internationalization\n")
+ testFiles(t, all, "testdata/i/j/k/k8s.txt", "kubernetes\n")
+ testFiles(t, all, "testdata/ken.txt", "If a program is too slow, it must have a loop.\n")
+
+ testDir(t, all, ".", "testdata/")
+ testDir(t, all, "testdata/i", "i18n.txt", "j/")
+ testDir(t, all, "testdata/i/j", "k/")
+ testDir(t, all, "testdata/i/j/k", "k8s.txt")
+}
+
+var (
+ //go:embed testdata
+ testHiddenDir embed.FS
+
+ //go:embed testdata/*
+ testHiddenStar embed.FS
+)
+
+func TestHidden(t *testing.T) {
+ dir := testHiddenDir
+ star := testHiddenStar
+
+ t.Logf("//go:embed testdata")
+
+ testDir(t, dir, "testdata",
+ "-not-hidden/", "ascii.txt", "glass.txt", "hello.txt", "i/", "ken.txt")
+
+ t.Logf("//go:embed testdata/*")
+
+ testDir(t, star, "testdata",
+ "-not-hidden/", ".hidden/", "_hidden/", "ascii.txt", "glass.txt", "hello.txt", "i/", "ken.txt")
+
+ testDir(t, star, "testdata/.hidden",
+ "fortune.txt", "more/") // but not .more or _more
+}
+
+func TestUninitialized(t *testing.T) {
+ var uninitialized embed.FS
+ testDir(t, uninitialized, ".")
+ f, err := uninitialized.Open(".")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+ fi, err := f.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !fi.IsDir() {
+ t.Errorf("in uninitialized embed.FS, . is not a directory")
+ }
+}
+
+var (
+ //go:embed "testdata/hello.txt"
+ helloT []T
+ //go:embed "testdata/hello.txt"
+ helloUint8 []uint8
+ //go:embed "testdata/hello.txt"
+ helloEUint8 []EmbedUint8
+ //go:embed "testdata/hello.txt"
+ helloBytes EmbedBytes
+ //go:embed "testdata/hello.txt"
+ helloString EmbedString
+)
+
+type T byte
+type EmbedUint8 uint8
+type EmbedBytes []byte
+type EmbedString string
+
+// golang.org/issue/47735
+func TestAliases(t *testing.T) {
+ all := testDirAll
+ want, e := all.ReadFile("testdata/hello.txt")
+ if e != nil {
+ t.Fatal("ReadFile:", e)
+ }
+ check := func(g any) {
+ got := reflect.ValueOf(g)
+ for i := 0; i < got.Len(); i++ {
+ if byte(got.Index(i).Uint()) != want[i] {
+ t.Fatalf("got %v want %v", got.Bytes(), want)
+ }
+ }
+ }
+ check(helloT)
+ check(helloUint8)
+ check(helloEUint8)
+ check(helloBytes)
+ check(helloString)
+}
diff --git a/src/embed/internal/embedtest/embedx_test.go b/src/embed/internal/embedtest/embedx_test.go
new file mode 100644
index 0000000..27fa116
--- /dev/null
+++ b/src/embed/internal/embedtest/embedx_test.go
@@ -0,0 +1,92 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package embedtest_test
+
+import (
+ "embed"
+ "os"
+ "testing"
+)
+
+var (
+ global2 = global
+ concurrency2 = concurrency
+ glass2 = glass
+ sbig2 = sbig
+ bbig2 = bbig
+)
+
+//go:embed testdata/*.txt
+var global embed.FS
+
+//go:embed c*txt
+var concurrency string
+
+//go:embed testdata/g*.txt
+var glass []byte
+
+//go:embed testdata/ascii.txt
+var sbig string
+
+//go:embed testdata/ascii.txt
+var bbig []byte
+
+func testFiles(t *testing.T, f embed.FS, name, data string) {
+ t.Helper()
+ d, err := f.ReadFile(name)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if string(d) != data {
+ t.Errorf("read %v = %q, want %q", name, d, data)
+ }
+}
+
+func testString(t *testing.T, s, name, data string) {
+ t.Helper()
+ if s != data {
+ t.Errorf("%v = %q, want %q", name, s, data)
+ }
+}
+
+func TestXGlobal(t *testing.T) {
+ testFiles(t, global, "testdata/hello.txt", "hello, world\n")
+ testString(t, concurrency, "concurrency", "Concurrency is not parallelism.\n")
+ testString(t, string(glass), "glass", "I can eat glass and it doesn't hurt me.\n")
+ testString(t, concurrency2, "concurrency2", "Concurrency is not parallelism.\n")
+ testString(t, string(glass2), "glass2", "I can eat glass and it doesn't hurt me.\n")
+
+ big, err := os.ReadFile("testdata/ascii.txt")
+ if err != nil {
+ t.Fatal(err)
+ }
+ testString(t, sbig, "sbig", string(big))
+ testString(t, sbig2, "sbig2", string(big))
+ testString(t, string(bbig), "bbig", string(big))
+ testString(t, string(bbig2), "bbig", string(big))
+
+ if t.Failed() {
+ return
+ }
+
+ // Could check &glass[0] == &glass2[0] but also want to make sure write does not fault
+ // (data must not be in read-only memory).
+ old := glass[0]
+ glass[0]++
+ if glass2[0] != glass[0] {
+ t.Fatalf("glass and glass2 do not share storage")
+ }
+ glass[0] = old
+
+ // Could check &bbig[0] == &bbig2[0] but also want to make sure write does not fault
+ // (data must not be in read-only memory).
+ old = bbig[0]
+ bbig[0]++
+ if bbig2[0] != bbig[0] {
+ t.Fatalf("bbig and bbig2 do not share storage")
+ }
+ bbig[0] = old
+}
diff --git a/src/embed/internal/embedtest/testdata/-not-hidden/fortune.txt b/src/embed/internal/embedtest/testdata/-not-hidden/fortune.txt
new file mode 100644
index 0000000..31f2013
--- /dev/null
+++ b/src/embed/internal/embedtest/testdata/-not-hidden/fortune.txt
@@ -0,0 +1,2 @@
+WARNING: terminal is not fully functional
+ - (press RETURN)
diff --git a/src/embed/internal/embedtest/testdata/.hidden/.more/tip.txt b/src/embed/internal/embedtest/testdata/.hidden/.more/tip.txt
new file mode 100644
index 0000000..71b9c69
--- /dev/null
+++ b/src/embed/internal/embedtest/testdata/.hidden/.more/tip.txt
@@ -0,0 +1 @@
+#define struct union /* Great space saver */
diff --git a/src/embed/internal/embedtest/testdata/.hidden/_more/tip.txt b/src/embed/internal/embedtest/testdata/.hidden/_more/tip.txt
new file mode 100644
index 0000000..71b9c69
--- /dev/null
+++ b/src/embed/internal/embedtest/testdata/.hidden/_more/tip.txt
@@ -0,0 +1 @@
+#define struct union /* Great space saver */
diff --git a/src/embed/internal/embedtest/testdata/.hidden/fortune.txt b/src/embed/internal/embedtest/testdata/.hidden/fortune.txt
new file mode 100644
index 0000000..31f2013
--- /dev/null
+++ b/src/embed/internal/embedtest/testdata/.hidden/fortune.txt
@@ -0,0 +1,2 @@
+WARNING: terminal is not fully functional
+ - (press RETURN)
diff --git a/src/embed/internal/embedtest/testdata/.hidden/more/tip.txt b/src/embed/internal/embedtest/testdata/.hidden/more/tip.txt
new file mode 100644
index 0000000..71b9c69
--- /dev/null
+++ b/src/embed/internal/embedtest/testdata/.hidden/more/tip.txt
@@ -0,0 +1 @@
+#define struct union /* Great space saver */
diff --git a/src/embed/internal/embedtest/testdata/_hidden/fortune.txt b/src/embed/internal/embedtest/testdata/_hidden/fortune.txt
new file mode 100644
index 0000000..31f2013
--- /dev/null
+++ b/src/embed/internal/embedtest/testdata/_hidden/fortune.txt
@@ -0,0 +1,2 @@
+WARNING: terminal is not fully functional
+ - (press RETURN)
diff --git a/src/embed/internal/embedtest/testdata/ascii.txt b/src/embed/internal/embedtest/testdata/ascii.txt
new file mode 100644
index 0000000..0cfebf6
--- /dev/null
+++ b/src/embed/internal/embedtest/testdata/ascii.txt
@@ -0,0 +1,25 @@
+ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmn
+!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno
+"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop
+#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopq
+$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqr
+%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrs
+&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrst
+'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstu
+()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuv
+)*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvw
+*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwx
++,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxy
+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz
+-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{
+./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|
+/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
+0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
+123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !
+23456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !"
+3456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !"#
+456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !"#$
+56789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !"#$%
+6789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !"#$%&
+789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !"#$%&'
+89:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} !"#$%&'(
diff --git a/src/embed/internal/embedtest/testdata/glass.txt b/src/embed/internal/embedtest/testdata/glass.txt
new file mode 100644
index 0000000..8350baf
--- /dev/null
+++ b/src/embed/internal/embedtest/testdata/glass.txt
@@ -0,0 +1 @@
+I can eat glass and it doesn't hurt me.
diff --git a/src/embed/internal/embedtest/testdata/hello.txt b/src/embed/internal/embedtest/testdata/hello.txt
new file mode 100644
index 0000000..4b5fa63
--- /dev/null
+++ b/src/embed/internal/embedtest/testdata/hello.txt
@@ -0,0 +1 @@
+hello, world
diff --git a/src/embed/internal/embedtest/testdata/i/i18n.txt b/src/embed/internal/embedtest/testdata/i/i18n.txt
new file mode 100644
index 0000000..5ee27c6
--- /dev/null
+++ b/src/embed/internal/embedtest/testdata/i/i18n.txt
@@ -0,0 +1 @@
+internationalization
diff --git a/src/embed/internal/embedtest/testdata/i/j/k/k8s.txt b/src/embed/internal/embedtest/testdata/i/j/k/k8s.txt
new file mode 100644
index 0000000..807e21b
--- /dev/null
+++ b/src/embed/internal/embedtest/testdata/i/j/k/k8s.txt
@@ -0,0 +1 @@
+kubernetes
diff --git a/src/embed/internal/embedtest/testdata/ken.txt b/src/embed/internal/embedtest/testdata/ken.txt
new file mode 100644
index 0000000..bb25981
--- /dev/null
+++ b/src/embed/internal/embedtest/testdata/ken.txt
@@ -0,0 +1 @@
+If a program is too slow, it must have a loop.