Remove pkg testutil tempfile
Victor Vieux authored on 2017/08/25 07:24:15... | ... |
@@ -3,14 +3,14 @@ package dockerfile |
3 | 3 |
import ( |
4 | 4 |
"testing" |
5 | 5 |
|
6 |
- "github.com/docker/docker/pkg/testutil/tempfile" |
|
6 |
+ "github.com/gotestyourself/gotestyourself/fs" |
|
7 | 7 |
"github.com/stretchr/testify/assert" |
8 | 8 |
) |
9 | 9 |
|
10 | 10 |
func TestIsExistingDirectory(t *testing.T) { |
11 |
- tmpfile := tempfile.NewTempFile(t, "file-exists-test", "something") |
|
11 |
+ tmpfile := fs.NewFile(t, "file-exists-test", fs.WithContent("something")) |
|
12 | 12 |
defer tmpfile.Remove() |
13 |
- tmpdir := tempfile.NewTempDir(t, "dir-exists-test") |
|
13 |
+ tmpdir := fs.NewDir(t, "dir-exists-test") |
|
14 | 14 |
defer tmpdir.Remove() |
15 | 15 |
|
16 | 16 |
var testcases = []struct { |
... | ... |
@@ -20,7 +20,7 @@ func TestIsExistingDirectory(t *testing.T) { |
20 | 20 |
}{ |
21 | 21 |
{ |
22 | 22 |
doc: "directory exists", |
23 |
- path: tmpdir.Path, |
|
23 |
+ path: tmpdir.Path(), |
|
24 | 24 |
expected: true, |
25 | 25 |
}, |
26 | 26 |
{ |
... | ... |
@@ -30,7 +30,7 @@ func TestIsExistingDirectory(t *testing.T) { |
30 | 30 |
}, |
31 | 31 |
{ |
32 | 32 |
doc: "file exists", |
33 |
- path: tmpfile.Name(), |
|
33 |
+ path: tmpfile.Path(), |
|
34 | 34 |
expected: false, |
35 | 35 |
}, |
36 | 36 |
} |
... | ... |
@@ -5,7 +5,7 @@ import ( |
5 | 5 |
|
6 | 6 |
"github.com/docker/docker/daemon/config" |
7 | 7 |
"github.com/docker/docker/pkg/testutil" |
8 |
- "github.com/docker/docker/pkg/testutil/tempfile" |
|
8 |
+ "github.com/gotestyourself/gotestyourself/fs" |
|
9 | 9 |
"github.com/sirupsen/logrus" |
10 | 10 |
"github.com/spf13/pflag" |
11 | 11 |
"github.com/stretchr/testify/assert" |
... | ... |
@@ -46,9 +46,9 @@ func TestLoadDaemonCliConfigWithTLS(t *testing.T) { |
46 | 46 |
} |
47 | 47 |
|
48 | 48 |
func TestLoadDaemonCliConfigWithConflicts(t *testing.T) { |
49 |
- tempFile := tempfile.NewTempFile(t, "config", `{"labels": ["l3=foo"]}`) |
|
49 |
+ tempFile := fs.NewFile(t, "config", fs.WithContent(`{"labels": ["l3=foo"]}`)) |
|
50 | 50 |
defer tempFile.Remove() |
51 |
- configFile := tempFile.Name() |
|
51 |
+ configFile := tempFile.Path() |
|
52 | 52 |
|
53 | 53 |
opts := defaultOptions(configFile) |
54 | 54 |
flags := opts.flags |
... | ... |
@@ -62,10 +62,10 @@ func TestLoadDaemonCliConfigWithConflicts(t *testing.T) { |
62 | 62 |
} |
63 | 63 |
|
64 | 64 |
func TestLoadDaemonCliConfigWithTLSVerify(t *testing.T) { |
65 |
- tempFile := tempfile.NewTempFile(t, "config", `{"tlsverify": true}`) |
|
65 |
+ tempFile := fs.NewFile(t, "config", fs.WithContent(`{"tlsverify": true}`)) |
|
66 | 66 |
defer tempFile.Remove() |
67 | 67 |
|
68 |
- opts := defaultOptions(tempFile.Name()) |
|
68 |
+ opts := defaultOptions(tempFile.Path()) |
|
69 | 69 |
opts.TLSOptions.CAFile = "/tmp/ca.pem" |
70 | 70 |
|
71 | 71 |
loadedConfig, err := loadDaemonCliConfig(opts) |
... | ... |
@@ -75,10 +75,10 @@ func TestLoadDaemonCliConfigWithTLSVerify(t *testing.T) { |
75 | 75 |
} |
76 | 76 |
|
77 | 77 |
func TestLoadDaemonCliConfigWithExplicitTLSVerifyFalse(t *testing.T) { |
78 |
- tempFile := tempfile.NewTempFile(t, "config", `{"tlsverify": false}`) |
|
78 |
+ tempFile := fs.NewFile(t, "config", fs.WithContent(`{"tlsverify": false}`)) |
|
79 | 79 |
defer tempFile.Remove() |
80 | 80 |
|
81 |
- opts := defaultOptions(tempFile.Name()) |
|
81 |
+ opts := defaultOptions(tempFile.Path()) |
|
82 | 82 |
opts.TLSOptions.CAFile = "/tmp/ca.pem" |
83 | 83 |
|
84 | 84 |
loadedConfig, err := loadDaemonCliConfig(opts) |
... | ... |
@@ -88,10 +88,10 @@ func TestLoadDaemonCliConfigWithExplicitTLSVerifyFalse(t *testing.T) { |
88 | 88 |
} |
89 | 89 |
|
90 | 90 |
func TestLoadDaemonCliConfigWithoutTLSVerify(t *testing.T) { |
91 |
- tempFile := tempfile.NewTempFile(t, "config", `{}`) |
|
91 |
+ tempFile := fs.NewFile(t, "config", fs.WithContent(`{}`)) |
|
92 | 92 |
defer tempFile.Remove() |
93 | 93 |
|
94 |
- opts := defaultOptions(tempFile.Name()) |
|
94 |
+ opts := defaultOptions(tempFile.Path()) |
|
95 | 95 |
opts.TLSOptions.CAFile = "/tmp/ca.pem" |
96 | 96 |
|
97 | 97 |
loadedConfig, err := loadDaemonCliConfig(opts) |
... | ... |
@@ -101,10 +101,10 @@ func TestLoadDaemonCliConfigWithoutTLSVerify(t *testing.T) { |
101 | 101 |
} |
102 | 102 |
|
103 | 103 |
func TestLoadDaemonCliConfigWithLogLevel(t *testing.T) { |
104 |
- tempFile := tempfile.NewTempFile(t, "config", `{"log-level": "warn"}`) |
|
104 |
+ tempFile := fs.NewFile(t, "config", fs.WithContent(`{"log-level": "warn"}`)) |
|
105 | 105 |
defer tempFile.Remove() |
106 | 106 |
|
107 |
- opts := defaultOptions(tempFile.Name()) |
|
107 |
+ opts := defaultOptions(tempFile.Path()) |
|
108 | 108 |
loadedConfig, err := loadDaemonCliConfig(opts) |
109 | 109 |
require.NoError(t, err) |
110 | 110 |
require.NotNil(t, loadedConfig) |
... | ... |
@@ -114,10 +114,10 @@ func TestLoadDaemonCliConfigWithLogLevel(t *testing.T) { |
114 | 114 |
|
115 | 115 |
func TestLoadDaemonConfigWithEmbeddedOptions(t *testing.T) { |
116 | 116 |
content := `{"tlscacert": "/etc/certs/ca.pem", "log-driver": "syslog"}` |
117 |
- tempFile := tempfile.NewTempFile(t, "config", content) |
|
117 |
+ tempFile := fs.NewFile(t, "config", fs.WithContent(content)) |
|
118 | 118 |
defer tempFile.Remove() |
119 | 119 |
|
120 |
- opts := defaultOptions(tempFile.Name()) |
|
120 |
+ opts := defaultOptions(tempFile.Path()) |
|
121 | 121 |
loadedConfig, err := loadDaemonCliConfig(opts) |
122 | 122 |
require.NoError(t, err) |
123 | 123 |
require.NotNil(t, loadedConfig) |
... | ... |
@@ -131,10 +131,10 @@ func TestLoadDaemonConfigWithRegistryOptions(t *testing.T) { |
131 | 131 |
"registry-mirrors": ["https://mirrors.docker.com"], |
132 | 132 |
"insecure-registries": ["https://insecure.docker.com"] |
133 | 133 |
}` |
134 |
- tempFile := tempfile.NewTempFile(t, "config", content) |
|
134 |
+ tempFile := fs.NewFile(t, "config", fs.WithContent(content)) |
|
135 | 135 |
defer tempFile.Remove() |
136 | 136 |
|
137 |
- opts := defaultOptions(tempFile.Name()) |
|
137 |
+ opts := defaultOptions(tempFile.Path()) |
|
138 | 138 |
loadedConfig, err := loadDaemonCliConfig(opts) |
139 | 139 |
require.NoError(t, err) |
140 | 140 |
require.NotNil(t, loadedConfig) |
... | ... |
@@ -9,17 +9,17 @@ import ( |
9 | 9 |
"testing" |
10 | 10 |
|
11 | 11 |
"github.com/docker/docker/daemon/config" |
12 |
- "github.com/docker/docker/pkg/testutil/tempfile" |
|
12 |
+ "github.com/gotestyourself/gotestyourself/fs" |
|
13 | 13 |
"github.com/stretchr/testify/assert" |
14 | 14 |
"github.com/stretchr/testify/require" |
15 | 15 |
) |
16 | 16 |
|
17 | 17 |
func TestLoadDaemonCliConfigWithDaemonFlags(t *testing.T) { |
18 | 18 |
content := `{"log-opts": {"max-size": "1k"}}` |
19 |
- tempFile := tempfile.NewTempFile(t, "config", content) |
|
19 |
+ tempFile := fs.NewFile(t, "config", fs.WithContent(content)) |
|
20 | 20 |
defer tempFile.Remove() |
21 | 21 |
|
22 |
- opts := defaultOptions(tempFile.Name()) |
|
22 |
+ opts := defaultOptions(tempFile.Path()) |
|
23 | 23 |
opts.Debug = true |
24 | 24 |
opts.LogLevel = "info" |
25 | 25 |
assert.NoError(t, opts.flags.Set("selinux-enabled", "true")) |
... | ... |
@@ -37,10 +37,10 @@ func TestLoadDaemonCliConfigWithDaemonFlags(t *testing.T) { |
37 | 37 |
|
38 | 38 |
func TestLoadDaemonConfigWithNetwork(t *testing.T) { |
39 | 39 |
content := `{"bip": "127.0.0.2", "ip": "127.0.0.1"}` |
40 |
- tempFile := tempfile.NewTempFile(t, "config", content) |
|
40 |
+ tempFile := fs.NewFile(t, "config", fs.WithContent(content)) |
|
41 | 41 |
defer tempFile.Remove() |
42 | 42 |
|
43 |
- opts := defaultOptions(tempFile.Name()) |
|
43 |
+ opts := defaultOptions(tempFile.Path()) |
|
44 | 44 |
loadedConfig, err := loadDaemonCliConfig(opts) |
45 | 45 |
require.NoError(t, err) |
46 | 46 |
require.NotNil(t, loadedConfig) |
... | ... |
@@ -54,10 +54,10 @@ func TestLoadDaemonConfigWithMapOptions(t *testing.T) { |
54 | 54 |
"cluster-store-opts": {"kv.cacertfile": "/var/lib/docker/discovery_certs/ca.pem"}, |
55 | 55 |
"log-opts": {"tag": "test"} |
56 | 56 |
}` |
57 |
- tempFile := tempfile.NewTempFile(t, "config", content) |
|
57 |
+ tempFile := fs.NewFile(t, "config", fs.WithContent(content)) |
|
58 | 58 |
defer tempFile.Remove() |
59 | 59 |
|
60 |
- opts := defaultOptions(tempFile.Name()) |
|
60 |
+ opts := defaultOptions(tempFile.Path()) |
|
61 | 61 |
loadedConfig, err := loadDaemonCliConfig(opts) |
62 | 62 |
require.NoError(t, err) |
63 | 63 |
require.NotNil(t, loadedConfig) |
... | ... |
@@ -71,10 +71,10 @@ func TestLoadDaemonConfigWithMapOptions(t *testing.T) { |
71 | 71 |
|
72 | 72 |
func TestLoadDaemonConfigWithTrueDefaultValues(t *testing.T) { |
73 | 73 |
content := `{ "userland-proxy": false }` |
74 |
- tempFile := tempfile.NewTempFile(t, "config", content) |
|
74 |
+ tempFile := fs.NewFile(t, "config", fs.WithContent(content)) |
|
75 | 75 |
defer tempFile.Remove() |
76 | 76 |
|
77 |
- opts := defaultOptions(tempFile.Name()) |
|
77 |
+ opts := defaultOptions(tempFile.Path()) |
|
78 | 78 |
loadedConfig, err := loadDaemonCliConfig(opts) |
79 | 79 |
require.NoError(t, err) |
80 | 80 |
require.NotNil(t, loadedConfig) |
... | ... |
@@ -90,10 +90,10 @@ func TestLoadDaemonConfigWithTrueDefaultValues(t *testing.T) { |
90 | 90 |
} |
91 | 91 |
|
92 | 92 |
func TestLoadDaemonConfigWithTrueDefaultValuesLeaveDefaults(t *testing.T) { |
93 |
- tempFile := tempfile.NewTempFile(t, "config", `{}`) |
|
93 |
+ tempFile := fs.NewFile(t, "config", fs.WithContent(`{}`)) |
|
94 | 94 |
defer tempFile.Remove() |
95 | 95 |
|
96 |
- opts := defaultOptions(tempFile.Name()) |
|
96 |
+ opts := defaultOptions(tempFile.Path()) |
|
97 | 97 |
loadedConfig, err := loadDaemonCliConfig(opts) |
98 | 98 |
require.NoError(t, err) |
99 | 99 |
require.NotNil(t, loadedConfig) |
... | ... |
@@ -103,10 +103,10 @@ func TestLoadDaemonConfigWithTrueDefaultValuesLeaveDefaults(t *testing.T) { |
103 | 103 |
|
104 | 104 |
func TestLoadDaemonConfigWithLegacyRegistryOptions(t *testing.T) { |
105 | 105 |
content := `{"disable-legacy-registry": false}` |
106 |
- tempFile := tempfile.NewTempFile(t, "config", content) |
|
106 |
+ tempFile := fs.NewFile(t, "config", fs.WithContent(content)) |
|
107 | 107 |
defer tempFile.Remove() |
108 | 108 |
|
109 |
- opts := defaultOptions(tempFile.Name()) |
|
109 |
+ opts := defaultOptions(tempFile.Path()) |
|
110 | 110 |
loadedConfig, err := loadDaemonCliConfig(opts) |
111 | 111 |
require.NoError(t, err) |
112 | 112 |
require.NotNil(t, loadedConfig) |
... | ... |
@@ -6,8 +6,8 @@ import ( |
6 | 6 |
"testing" |
7 | 7 |
|
8 | 8 |
"github.com/docker/docker/opts" |
9 |
- "github.com/docker/docker/pkg/testutil/tempfile" |
|
10 | 9 |
"github.com/docker/go-units" |
10 |
+ "github.com/gotestyourself/gotestyourself/fs" |
|
11 | 11 |
"github.com/spf13/pflag" |
12 | 12 |
"github.com/stretchr/testify/assert" |
13 | 13 |
"github.com/stretchr/testify/require" |
... | ... |
@@ -29,7 +29,7 @@ func TestGetConflictFreeConfiguration(t *testing.T) { |
29 | 29 |
} |
30 | 30 |
}`)) |
31 | 31 |
|
32 |
- file := tempfile.NewTempFile(t, "docker-config", configFileData) |
|
32 |
+ file := fs.NewFile(t, "docker-config", fs.WithContent(configFileData)) |
|
33 | 33 |
defer file.Remove() |
34 | 34 |
|
35 | 35 |
flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
... | ... |
@@ -38,7 +38,7 @@ func TestGetConflictFreeConfiguration(t *testing.T) { |
38 | 38 |
flags.Var(opts.NewNamedUlimitOpt("default-ulimits", nil), "default-ulimit", "") |
39 | 39 |
flags.Var(opts.NewNamedMapOpts("log-opts", nil, nil), "log-opt", "") |
40 | 40 |
|
41 |
- cc, err := getConflictFreeConfiguration(file.Name(), flags) |
|
41 |
+ cc, err := getConflictFreeConfiguration(file.Path(), flags) |
|
42 | 42 |
require.NoError(t, err) |
43 | 43 |
|
44 | 44 |
assert.True(t, cc.Debug) |
... | ... |
@@ -70,7 +70,7 @@ func TestDaemonConfigurationMerge(t *testing.T) { |
70 | 70 |
} |
71 | 71 |
}`)) |
72 | 72 |
|
73 |
- file := tempfile.NewTempFile(t, "docker-config", configFileData) |
|
73 |
+ file := fs.NewFile(t, "docker-config", fs.WithContent(configFileData)) |
|
74 | 74 |
defer file.Remove() |
75 | 75 |
|
76 | 76 |
c := &Config{ |
... | ... |
@@ -90,7 +90,7 @@ func TestDaemonConfigurationMerge(t *testing.T) { |
90 | 90 |
flags.Var(opts.NewNamedUlimitOpt("default-ulimits", nil), "default-ulimit", "") |
91 | 91 |
flags.Var(opts.NewNamedMapOpts("log-opts", nil, nil), "log-opt", "") |
92 | 92 |
|
93 |
- cc, err := MergeDaemonConfigurations(c, flags, file.Name()) |
|
93 |
+ cc, err := MergeDaemonConfigurations(c, flags, file.Path()) |
|
94 | 94 |
require.NoError(t, err) |
95 | 95 |
|
96 | 96 |
assert.True(t, cc.Debug) |
... | ... |
@@ -120,7 +120,7 @@ func TestDaemonConfigurationMergeShmSize(t *testing.T) { |
120 | 120 |
"default-shm-size": "1g" |
121 | 121 |
}`)) |
122 | 122 |
|
123 |
- file := tempfile.NewTempFile(t, "docker-config", data) |
|
123 |
+ file := fs.NewFile(t, "docker-config", fs.WithContent(data)) |
|
124 | 124 |
defer file.Remove() |
125 | 125 |
|
126 | 126 |
c := &Config{} |
... | ... |
@@ -129,7 +129,7 @@ func TestDaemonConfigurationMergeShmSize(t *testing.T) { |
129 | 129 |
shmSize := opts.MemBytes(DefaultShmSize) |
130 | 130 |
flags.Var(&shmSize, "default-shm-size", "") |
131 | 131 |
|
132 |
- cc, err := MergeDaemonConfigurations(c, flags, file.Name()) |
|
132 |
+ cc, err := MergeDaemonConfigurations(c, flags, file.Path()) |
|
133 | 133 |
require.NoError(t, err) |
134 | 134 |
|
135 | 135 |
expectedValue := 1 * 1024 * 1024 * 1024 |
... | ... |
@@ -23,11 +23,11 @@ import ( |
23 | 23 |
"github.com/docker/docker/integration-cli/cli" |
24 | 24 |
"github.com/docker/docker/integration-cli/daemon" |
25 | 25 |
icmd "github.com/docker/docker/pkg/testutil/cmd" |
26 |
- "github.com/docker/docker/pkg/testutil/tempfile" |
|
27 | 26 |
"github.com/docker/libnetwork/driverapi" |
28 | 27 |
"github.com/docker/libnetwork/ipamapi" |
29 | 28 |
remoteipam "github.com/docker/libnetwork/ipams/remote/api" |
30 | 29 |
"github.com/go-check/check" |
30 |
+ "github.com/gotestyourself/gotestyourself/fs" |
|
31 | 31 |
"github.com/vishvananda/netlink" |
32 | 32 |
"golang.org/x/net/context" |
33 | 33 |
) |
... | ... |
@@ -68,11 +68,11 @@ func (s *DockerSwarmSuite) TestSwarmUpdate(c *check.C) { |
68 | 68 |
c.Assert(spec.CAConfig.ExternalCAs[1].CACert, checker.Equals, string(expected)) |
69 | 69 |
|
70 | 70 |
// passing an invalid external CA fails |
71 |
- tempFile := tempfile.NewTempFile(c, "testfile", "fakecert") |
|
71 |
+ tempFile := fs.NewFile(c, "testfile", fs.WithContent("fakecert")) |
|
72 | 72 |
defer tempFile.Remove() |
73 | 73 |
|
74 | 74 |
result := cli.Docker(cli.Args("swarm", "update", |
75 |
- "--external-ca", fmt.Sprintf("protocol=cfssl,url=https://something.org,cacert=%s", tempFile.Name())), |
|
75 |
+ "--external-ca", fmt.Sprintf("protocol=cfssl,url=https://something.org,cacert=%s", tempFile.Path())), |
|
76 | 76 |
cli.Daemon(d.Daemon)) |
77 | 77 |
result.Assert(c, icmd.Expected{ |
78 | 78 |
ExitCode: 125, |
... | ... |
@@ -89,11 +89,11 @@ func (s *DockerSwarmSuite) TestSwarmInit(c *check.C) { |
89 | 89 |
} |
90 | 90 |
|
91 | 91 |
// passing an invalid external CA fails |
92 |
- tempFile := tempfile.NewTempFile(c, "testfile", "fakecert") |
|
92 |
+ tempFile := fs.NewFile(c, "testfile", fs.WithContent("fakecert")) |
|
93 | 93 |
defer tempFile.Remove() |
94 | 94 |
|
95 | 95 |
result := cli.Docker(cli.Args("swarm", "init", "--cert-expiry", "30h", "--dispatcher-heartbeat", "11s", |
96 |
- "--external-ca", fmt.Sprintf("protocol=cfssl,url=https://somethingelse.org,cacert=%s", tempFile.Name())), |
|
96 |
+ "--external-ca", fmt.Sprintf("protocol=cfssl,url=https://somethingelse.org,cacert=%s", tempFile.Path())), |
|
97 | 97 |
cli.Daemon(d.Daemon)) |
98 | 98 |
result.Assert(c, icmd.Expected{ |
99 | 99 |
ExitCode: 125, |
100 | 100 |
deleted file mode 100644 |
... | ... |
@@ -1,28 +0,0 @@ |
1 |
-// Package golden provides function and helpers to use golden file for |
|
2 |
-// testing purpose. |
|
3 |
-package golden |
|
4 |
- |
|
5 |
-import ( |
|
6 |
- "flag" |
|
7 |
- "io/ioutil" |
|
8 |
- "path/filepath" |
|
9 |
- "testing" |
|
10 |
-) |
|
11 |
- |
|
12 |
-var update = flag.Bool("test.update", false, "update golden file") |
|
13 |
- |
|
14 |
-// Get returns the golden file content. If the `test.update` is specified, it updates the |
|
15 |
-// file with the current output and returns it. |
|
16 |
-func Get(t *testing.T, actual []byte, filename string) []byte { |
|
17 |
- golden := filepath.Join("testdata", filename) |
|
18 |
- if *update { |
|
19 |
- if err := ioutil.WriteFile(golden, actual, 0644); err != nil { |
|
20 |
- t.Fatal(err) |
|
21 |
- } |
|
22 |
- } |
|
23 |
- expected, err := ioutil.ReadFile(golden) |
|
24 |
- if err != nil { |
|
25 |
- t.Fatal(err) |
|
26 |
- } |
|
27 |
- return expected |
|
28 |
-} |
29 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,56 +0,0 @@ |
1 |
-package tempfile |
|
2 |
- |
|
3 |
-import ( |
|
4 |
- "io/ioutil" |
|
5 |
- "os" |
|
6 |
- |
|
7 |
- "github.com/stretchr/testify/require" |
|
8 |
-) |
|
9 |
- |
|
10 |
-// TempFile is a temporary file that can be used with unit tests. TempFile |
|
11 |
-// reduces the boilerplate setup required in each test case by handling |
|
12 |
-// setup errors. |
|
13 |
-type TempFile struct { |
|
14 |
- File *os.File |
|
15 |
-} |
|
16 |
- |
|
17 |
-// NewTempFile returns a new temp file with contents |
|
18 |
-func NewTempFile(t require.TestingT, prefix string, content string) *TempFile { |
|
19 |
- file, err := ioutil.TempFile("", prefix+"-") |
|
20 |
- require.NoError(t, err) |
|
21 |
- |
|
22 |
- _, err = file.Write([]byte(content)) |
|
23 |
- require.NoError(t, err) |
|
24 |
- file.Close() |
|
25 |
- return &TempFile{File: file} |
|
26 |
-} |
|
27 |
- |
|
28 |
-// Name returns the filename |
|
29 |
-func (f *TempFile) Name() string { |
|
30 |
- return f.File.Name() |
|
31 |
-} |
|
32 |
- |
|
33 |
-// Remove removes the file |
|
34 |
-func (f *TempFile) Remove() { |
|
35 |
- os.Remove(f.Name()) |
|
36 |
-} |
|
37 |
- |
|
38 |
-// TempDir is a temporary directory that can be used with unit tests. TempDir |
|
39 |
-// reduces the boilerplate setup required in each test case by handling |
|
40 |
-// setup errors. |
|
41 |
-type TempDir struct { |
|
42 |
- Path string |
|
43 |
-} |
|
44 |
- |
|
45 |
-// NewTempDir returns a new temp file with contents |
|
46 |
-func NewTempDir(t require.TestingT, prefix string) *TempDir { |
|
47 |
- path, err := ioutil.TempDir("", prefix+"-") |
|
48 |
- require.NoError(t, err) |
|
49 |
- |
|
50 |
- return &TempDir{Path: path} |
|
51 |
-} |
|
52 |
- |
|
53 |
-// Remove removes the file |
|
54 |
-func (f *TempDir) Remove() { |
|
55 |
- os.Remove(f.Path) |
|
56 |
-} |
... | ... |
@@ -21,6 +21,7 @@ github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d |
21 | 21 |
golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756 |
22 | 22 |
github.com/stretchr/testify 4d4bfba8f1d1027c4fdbe371823030df51419987 |
23 | 23 |
github.com/pmezard/go-difflib v1.0.0 |
24 |
+github.com/gotestyourself/gotestyourself v1.1.0 |
|
24 | 25 |
|
25 | 26 |
github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5 |
26 | 27 |
github.com/imdario/mergo 0.2.1 |
27 | 28 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,202 @@ |
0 |
+ |
|
1 |
+ Apache License |
|
2 |
+ Version 2.0, January 2004 |
|
3 |
+ http://www.apache.org/licenses/ |
|
4 |
+ |
|
5 |
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
|
6 |
+ |
|
7 |
+ 1. Definitions. |
|
8 |
+ |
|
9 |
+ "License" shall mean the terms and conditions for use, reproduction, |
|
10 |
+ and distribution as defined by Sections 1 through 9 of this document. |
|
11 |
+ |
|
12 |
+ "Licensor" shall mean the copyright owner or entity authorized by |
|
13 |
+ the copyright owner that is granting the License. |
|
14 |
+ |
|
15 |
+ "Legal Entity" shall mean the union of the acting entity and all |
|
16 |
+ other entities that control, are controlled by, or are under common |
|
17 |
+ control with that entity. For the purposes of this definition, |
|
18 |
+ "control" means (i) the power, direct or indirect, to cause the |
|
19 |
+ direction or management of such entity, whether by contract or |
|
20 |
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the |
|
21 |
+ outstanding shares, or (iii) beneficial ownership of such entity. |
|
22 |
+ |
|
23 |
+ "You" (or "Your") shall mean an individual or Legal Entity |
|
24 |
+ exercising permissions granted by this License. |
|
25 |
+ |
|
26 |
+ "Source" form shall mean the preferred form for making modifications, |
|
27 |
+ including but not limited to software source code, documentation |
|
28 |
+ source, and configuration files. |
|
29 |
+ |
|
30 |
+ "Object" form shall mean any form resulting from mechanical |
|
31 |
+ transformation or translation of a Source form, including but |
|
32 |
+ not limited to compiled object code, generated documentation, |
|
33 |
+ and conversions to other media types. |
|
34 |
+ |
|
35 |
+ "Work" shall mean the work of authorship, whether in Source or |
|
36 |
+ Object form, made available under the License, as indicated by a |
|
37 |
+ copyright notice that is included in or attached to the work |
|
38 |
+ (an example is provided in the Appendix below). |
|
39 |
+ |
|
40 |
+ "Derivative Works" shall mean any work, whether in Source or Object |
|
41 |
+ form, that is based on (or derived from) the Work and for which the |
|
42 |
+ editorial revisions, annotations, elaborations, or other modifications |
|
43 |
+ represent, as a whole, an original work of authorship. For the purposes |
|
44 |
+ of this License, Derivative Works shall not include works that remain |
|
45 |
+ separable from, or merely link (or bind by name) to the interfaces of, |
|
46 |
+ the Work and Derivative Works thereof. |
|
47 |
+ |
|
48 |
+ "Contribution" shall mean any work of authorship, including |
|
49 |
+ the original version of the Work and any modifications or additions |
|
50 |
+ to that Work or Derivative Works thereof, that is intentionally |
|
51 |
+ submitted to Licensor for inclusion in the Work by the copyright owner |
|
52 |
+ or by an individual or Legal Entity authorized to submit on behalf of |
|
53 |
+ the copyright owner. For the purposes of this definition, "submitted" |
|
54 |
+ means any form of electronic, verbal, or written communication sent |
|
55 |
+ to the Licensor or its representatives, including but not limited to |
|
56 |
+ communication on electronic mailing lists, source code control systems, |
|
57 |
+ and issue tracking systems that are managed by, or on behalf of, the |
|
58 |
+ Licensor for the purpose of discussing and improving the Work, but |
|
59 |
+ excluding communication that is conspicuously marked or otherwise |
|
60 |
+ designated in writing by the copyright owner as "Not a Contribution." |
|
61 |
+ |
|
62 |
+ "Contributor" shall mean Licensor and any individual or Legal Entity |
|
63 |
+ on behalf of whom a Contribution has been received by Licensor and |
|
64 |
+ subsequently incorporated within the Work. |
|
65 |
+ |
|
66 |
+ 2. Grant of Copyright License. Subject to the terms and conditions of |
|
67 |
+ this License, each Contributor hereby grants to You a perpetual, |
|
68 |
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
|
69 |
+ copyright license to reproduce, prepare Derivative Works of, |
|
70 |
+ publicly display, publicly perform, sublicense, and distribute the |
|
71 |
+ Work and such Derivative Works in Source or Object form. |
|
72 |
+ |
|
73 |
+ 3. Grant of Patent License. Subject to the terms and conditions of |
|
74 |
+ this License, each Contributor hereby grants to You a perpetual, |
|
75 |
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
|
76 |
+ (except as stated in this section) patent license to make, have made, |
|
77 |
+ use, offer to sell, sell, import, and otherwise transfer the Work, |
|
78 |
+ where such license applies only to those patent claims licensable |
|
79 |
+ by such Contributor that are necessarily infringed by their |
|
80 |
+ Contribution(s) alone or by combination of their Contribution(s) |
|
81 |
+ with the Work to which such Contribution(s) was submitted. If You |
|
82 |
+ institute patent litigation against any entity (including a |
|
83 |
+ cross-claim or counterclaim in a lawsuit) alleging that the Work |
|
84 |
+ or a Contribution incorporated within the Work constitutes direct |
|
85 |
+ or contributory patent infringement, then any patent licenses |
|
86 |
+ granted to You under this License for that Work shall terminate |
|
87 |
+ as of the date such litigation is filed. |
|
88 |
+ |
|
89 |
+ 4. Redistribution. You may reproduce and distribute copies of the |
|
90 |
+ Work or Derivative Works thereof in any medium, with or without |
|
91 |
+ modifications, and in Source or Object form, provided that You |
|
92 |
+ meet the following conditions: |
|
93 |
+ |
|
94 |
+ (a) You must give any other recipients of the Work or |
|
95 |
+ Derivative Works a copy of this License; and |
|
96 |
+ |
|
97 |
+ (b) You must cause any modified files to carry prominent notices |
|
98 |
+ stating that You changed the files; and |
|
99 |
+ |
|
100 |
+ (c) You must retain, in the Source form of any Derivative Works |
|
101 |
+ that You distribute, all copyright, patent, trademark, and |
|
102 |
+ attribution notices from the Source form of the Work, |
|
103 |
+ excluding those notices that do not pertain to any part of |
|
104 |
+ the Derivative Works; and |
|
105 |
+ |
|
106 |
+ (d) If the Work includes a "NOTICE" text file as part of its |
|
107 |
+ distribution, then any Derivative Works that You distribute must |
|
108 |
+ include a readable copy of the attribution notices contained |
|
109 |
+ within such NOTICE file, excluding those notices that do not |
|
110 |
+ pertain to any part of the Derivative Works, in at least one |
|
111 |
+ of the following places: within a NOTICE text file distributed |
|
112 |
+ as part of the Derivative Works; within the Source form or |
|
113 |
+ documentation, if provided along with the Derivative Works; or, |
|
114 |
+ within a display generated by the Derivative Works, if and |
|
115 |
+ wherever such third-party notices normally appear. The contents |
|
116 |
+ of the NOTICE file are for informational purposes only and |
|
117 |
+ do not modify the License. You may add Your own attribution |
|
118 |
+ notices within Derivative Works that You distribute, alongside |
|
119 |
+ or as an addendum to the NOTICE text from the Work, provided |
|
120 |
+ that such additional attribution notices cannot be construed |
|
121 |
+ as modifying the License. |
|
122 |
+ |
|
123 |
+ You may add Your own copyright statement to Your modifications and |
|
124 |
+ may provide additional or different license terms and conditions |
|
125 |
+ for use, reproduction, or distribution of Your modifications, or |
|
126 |
+ for any such Derivative Works as a whole, provided Your use, |
|
127 |
+ reproduction, and distribution of the Work otherwise complies with |
|
128 |
+ the conditions stated in this License. |
|
129 |
+ |
|
130 |
+ 5. Submission of Contributions. Unless You explicitly state otherwise, |
|
131 |
+ any Contribution intentionally submitted for inclusion in the Work |
|
132 |
+ by You to the Licensor shall be under the terms and conditions of |
|
133 |
+ this License, without any additional terms or conditions. |
|
134 |
+ Notwithstanding the above, nothing herein shall supersede or modify |
|
135 |
+ the terms of any separate license agreement you may have executed |
|
136 |
+ with Licensor regarding such Contributions. |
|
137 |
+ |
|
138 |
+ 6. Trademarks. This License does not grant permission to use the trade |
|
139 |
+ names, trademarks, service marks, or product names of the Licensor, |
|
140 |
+ except as required for reasonable and customary use in describing the |
|
141 |
+ origin of the Work and reproducing the content of the NOTICE file. |
|
142 |
+ |
|
143 |
+ 7. Disclaimer of Warranty. Unless required by applicable law or |
|
144 |
+ agreed to in writing, Licensor provides the Work (and each |
|
145 |
+ Contributor provides its Contributions) on an "AS IS" BASIS, |
|
146 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
|
147 |
+ implied, including, without limitation, any warranties or conditions |
|
148 |
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
|
149 |
+ PARTICULAR PURPOSE. You are solely responsible for determining the |
|
150 |
+ appropriateness of using or redistributing the Work and assume any |
|
151 |
+ risks associated with Your exercise of permissions under this License. |
|
152 |
+ |
|
153 |
+ 8. Limitation of Liability. In no event and under no legal theory, |
|
154 |
+ whether in tort (including negligence), contract, or otherwise, |
|
155 |
+ unless required by applicable law (such as deliberate and grossly |
|
156 |
+ negligent acts) or agreed to in writing, shall any Contributor be |
|
157 |
+ liable to You for damages, including any direct, indirect, special, |
|
158 |
+ incidental, or consequential damages of any character arising as a |
|
159 |
+ result of this License or out of the use or inability to use the |
|
160 |
+ Work (including but not limited to damages for loss of goodwill, |
|
161 |
+ work stoppage, computer failure or malfunction, or any and all |
|
162 |
+ other commercial damages or losses), even if such Contributor |
|
163 |
+ has been advised of the possibility of such damages. |
|
164 |
+ |
|
165 |
+ 9. Accepting Warranty or Additional Liability. While redistributing |
|
166 |
+ the Work or Derivative Works thereof, You may choose to offer, |
|
167 |
+ and charge a fee for, acceptance of support, warranty, indemnity, |
|
168 |
+ or other liability obligations and/or rights consistent with this |
|
169 |
+ License. However, in accepting such obligations, You may act only |
|
170 |
+ on Your own behalf and on Your sole responsibility, not on behalf |
|
171 |
+ of any other Contributor, and only if You agree to indemnify, |
|
172 |
+ defend, and hold each Contributor harmless for any liability |
|
173 |
+ incurred by, or claims asserted against, such Contributor by reason |
|
174 |
+ of your accepting any such warranty or additional liability. |
|
175 |
+ |
|
176 |
+ END OF TERMS AND CONDITIONS |
|
177 |
+ |
|
178 |
+ APPENDIX: How to apply the Apache License to your work. |
|
179 |
+ |
|
180 |
+ To apply the Apache License to your work, attach the following |
|
181 |
+ boilerplate notice, with the fields enclosed by brackets "[]" |
|
182 |
+ replaced with your own identifying information. (Don't include |
|
183 |
+ the brackets!) The text should be enclosed in the appropriate |
|
184 |
+ comment syntax for the file format. We also recommend that a |
|
185 |
+ file or class name and description of purpose be included on the |
|
186 |
+ same "printed page" as the copyright notice for easier |
|
187 |
+ identification within third-party archives. |
|
188 |
+ |
|
189 |
+ Copyright [yyyy] [name of copyright owner] |
|
190 |
+ |
|
191 |
+ Licensed under the Apache License, Version 2.0 (the "License"); |
|
192 |
+ you may not use this file except in compliance with the License. |
|
193 |
+ You may obtain a copy of the License at |
|
194 |
+ |
|
195 |
+ http://www.apache.org/licenses/LICENSE-2.0 |
|
196 |
+ |
|
197 |
+ Unless required by applicable law or agreed to in writing, software |
|
198 |
+ distributed under the License is distributed on an "AS IS" BASIS, |
|
199 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
200 |
+ See the License for the specific language governing permissions and |
|
201 |
+ limitations under the License. |
0 | 202 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,33 @@ |
0 |
+# Go Test Yourself |
|
1 |
+ |
|
2 |
+A collection of packages compatible with `go test` to support common testing |
|
3 |
+patterns. |
|
4 |
+ |
|
5 |
+[![GoDoc](https://godoc.org/github.com/gotestyourself/gotestyourself?status.svg)](https://godoc.org/github.com/gotestyourself/gotestyourself) |
|
6 |
+[![CircleCI](https://circleci.com/gh/gotestyourself/gotestyourself/tree/master.svg?style=shield)](https://circleci.com/gh/gotestyourself/gotestyourself/tree/master) |
|
7 |
+[![Go Reportcard](https://goreportcard.com/badge/github.com/gotestyourself/gotestyourself)](https://goreportcard.com/report/github.com/gotestyourself/gotestyourself) |
|
8 |
+ |
|
9 |
+ |
|
10 |
+## Packages |
|
11 |
+ |
|
12 |
+* [fs](http://godoc.org/github.com/gotestyourself/gotestyourself/fs) - |
|
13 |
+ create test files and directories |
|
14 |
+* [golden](http://godoc.org/github.com/gotestyourself/gotestyourself/golden) - |
|
15 |
+ compare large multi-line strings |
|
16 |
+* [testsum](http://godoc.org/github.com/gotestyourself/gotestyourself/testsum) - |
|
17 |
+ a program to summarize `go test` output and test failures |
|
18 |
+* [icmd](http://godoc.org/github.com/gotestyourself/gotestyourself/icmd) - |
|
19 |
+ execute binaries and test the output |
|
20 |
+* [poll](http://godoc.org/github.com/gotestyourself/gotestyourself/poll) - |
|
21 |
+ test asynchronous code by polling until a desired state is reached |
|
22 |
+* [skip](http://godoc.org/github.com/gotestyourself/gotestyourself/skip) - |
|
23 |
+ skip tests based on conditions |
|
24 |
+ |
|
25 |
+## Related |
|
26 |
+ |
|
27 |
+* [testify/assert](https://godoc.org/github.com/stretchr/testify/assert) and |
|
28 |
+ [testify/require](https://godoc.org/github.com/stretchr/testify/require) - |
|
29 |
+ assertion libraries with common assertions |
|
30 |
+* [golang/mock](https://github.com/golang/mock) - generate mocks for interfaces |
|
31 |
+* [testify/suite](https://godoc.org/github.com/stretchr/testify/suite) - |
|
32 |
+ group test into suites to share common setup/teardown logic |
0 | 33 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,81 @@ |
0 |
+/*Package fs provides tools for creating and working with temporary files and |
|
1 |
+directories. |
|
2 |
+*/ |
|
3 |
+package fs |
|
4 |
+ |
|
5 |
+import ( |
|
6 |
+ "io/ioutil" |
|
7 |
+ "os" |
|
8 |
+ "path/filepath" |
|
9 |
+ |
|
10 |
+ "github.com/stretchr/testify/require" |
|
11 |
+) |
|
12 |
+ |
|
13 |
+// Path objects return their filesystem path. Both File and Dir implement Path. |
|
14 |
+type Path interface { |
|
15 |
+ Path() string |
|
16 |
+} |
|
17 |
+ |
|
18 |
+// File is a temporary file on the filesystem |
|
19 |
+type File struct { |
|
20 |
+ path string |
|
21 |
+} |
|
22 |
+ |
|
23 |
+// NewFile creates a new file in a temporary directory using prefix as part of |
|
24 |
+// the filename. The PathOps are applied to the before returning the File. |
|
25 |
+func NewFile(t require.TestingT, prefix string, ops ...PathOp) *File { |
|
26 |
+ tempfile, err := ioutil.TempFile("", prefix+"-") |
|
27 |
+ require.NoError(t, err) |
|
28 |
+ file := &File{path: tempfile.Name()} |
|
29 |
+ require.NoError(t, tempfile.Close()) |
|
30 |
+ |
|
31 |
+ for _, op := range ops { |
|
32 |
+ require.NoError(t, op(file)) |
|
33 |
+ } |
|
34 |
+ return file |
|
35 |
+} |
|
36 |
+ |
|
37 |
+// Path returns the full path to the file |
|
38 |
+func (f *File) Path() string { |
|
39 |
+ return f.path |
|
40 |
+} |
|
41 |
+ |
|
42 |
+// Remove the file |
|
43 |
+func (f *File) Remove() { |
|
44 |
+ // nolint: errcheck |
|
45 |
+ os.Remove(f.path) |
|
46 |
+} |
|
47 |
+ |
|
48 |
+// Dir is a temporary directory |
|
49 |
+type Dir struct { |
|
50 |
+ path string |
|
51 |
+} |
|
52 |
+ |
|
53 |
+// NewDir returns a new temporary directory using prefix as part of the directory |
|
54 |
+// name. The PathOps are applied before returning the Dir. |
|
55 |
+func NewDir(t require.TestingT, prefix string, ops ...PathOp) *Dir { |
|
56 |
+ path, err := ioutil.TempDir("", prefix+"-") |
|
57 |
+ require.NoError(t, err) |
|
58 |
+ dir := &Dir{path: path} |
|
59 |
+ |
|
60 |
+ for _, op := range ops { |
|
61 |
+ require.NoError(t, op(dir)) |
|
62 |
+ } |
|
63 |
+ return dir |
|
64 |
+} |
|
65 |
+ |
|
66 |
+// Path returns the full path to the directory |
|
67 |
+func (d *Dir) Path() string { |
|
68 |
+ return d.path |
|
69 |
+} |
|
70 |
+ |
|
71 |
+// Remove the directory |
|
72 |
+func (d *Dir) Remove() { |
|
73 |
+ // nolint: errcheck |
|
74 |
+ os.RemoveAll(d.path) |
|
75 |
+} |
|
76 |
+ |
|
77 |
+// Join returns a new path with this directory as the base of the path |
|
78 |
+func (d *Dir) Join(parts ...string) string { |
|
79 |
+ return filepath.Join(append([]string{d.Path()}, parts...)...) |
|
80 |
+} |
0 | 81 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,94 @@ |
0 |
+package fs |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "io/ioutil" |
|
4 |
+ "os" |
|
5 |
+ "path/filepath" |
|
6 |
+) |
|
7 |
+ |
|
8 |
+// PathOp is a function which accepts a Path to perform some operation |
|
9 |
+type PathOp func(path Path) error |
|
10 |
+ |
|
11 |
+// WithContent writes content to a file at Path |
|
12 |
+func WithContent(content string) PathOp { |
|
13 |
+ return func(path Path) error { |
|
14 |
+ return ioutil.WriteFile(path.Path(), []byte(content), 0644) |
|
15 |
+ } |
|
16 |
+} |
|
17 |
+ |
|
18 |
+// WithBytes write bytes to a file at Path |
|
19 |
+func WithBytes(raw []byte) PathOp { |
|
20 |
+ return func(path Path) error { |
|
21 |
+ return ioutil.WriteFile(path.Path(), raw, 0644) |
|
22 |
+ } |
|
23 |
+} |
|
24 |
+ |
|
25 |
+// AsUser changes ownership of the file system object at Path |
|
26 |
+func AsUser(uid, gid int) PathOp { |
|
27 |
+ return func(path Path) error { |
|
28 |
+ return os.Chown(path.Path(), uid, gid) |
|
29 |
+ } |
|
30 |
+} |
|
31 |
+ |
|
32 |
+// WithFile creates a file in the directory at path with content |
|
33 |
+func WithFile(filename, content string) PathOp { |
|
34 |
+ return func(path Path) error { |
|
35 |
+ return createFile(path.Path(), filename, content) |
|
36 |
+ } |
|
37 |
+} |
|
38 |
+ |
|
39 |
+func createFile(dir, filename, content string) error { |
|
40 |
+ fullpath := filepath.Join(dir, filepath.FromSlash(filename)) |
|
41 |
+ return ioutil.WriteFile(fullpath, []byte(content), 0644) |
|
42 |
+} |
|
43 |
+ |
|
44 |
+// WithFiles creates all the files in the directory at path with their content |
|
45 |
+func WithFiles(files map[string]string) PathOp { |
|
46 |
+ return func(path Path) error { |
|
47 |
+ for filename, content := range files { |
|
48 |
+ if err := createFile(path.Path(), filename, content); err != nil { |
|
49 |
+ return err |
|
50 |
+ } |
|
51 |
+ } |
|
52 |
+ return nil |
|
53 |
+ } |
|
54 |
+} |
|
55 |
+ |
|
56 |
+// FromDir copies the directory tree from the source path into the new Dir |
|
57 |
+func FromDir(source string) PathOp { |
|
58 |
+ return func(path Path) error { |
|
59 |
+ return copyDirectory(source, path.Path()) |
|
60 |
+ } |
|
61 |
+} |
|
62 |
+ |
|
63 |
+func copyDirectory(source, dest string) error { |
|
64 |
+ entries, err := ioutil.ReadDir(source) |
|
65 |
+ if err != nil { |
|
66 |
+ return err |
|
67 |
+ } |
|
68 |
+ for _, entry := range entries { |
|
69 |
+ sourcePath := filepath.Join(source, entry.Name()) |
|
70 |
+ destPath := filepath.Join(dest, entry.Name()) |
|
71 |
+ if entry.IsDir() { |
|
72 |
+ if err := os.Mkdir(destPath, 0755); err != nil { |
|
73 |
+ return err |
|
74 |
+ } |
|
75 |
+ if err := copyDirectory(sourcePath, destPath); err != nil { |
|
76 |
+ return err |
|
77 |
+ } |
|
78 |
+ continue |
|
79 |
+ } |
|
80 |
+ if err := copyFile(sourcePath, destPath); err != nil { |
|
81 |
+ return err |
|
82 |
+ } |
|
83 |
+ } |
|
84 |
+ return nil |
|
85 |
+} |
|
86 |
+ |
|
87 |
+func copyFile(source, dest string) error { |
|
88 |
+ content, err := ioutil.ReadFile(source) |
|
89 |
+ if err != nil { |
|
90 |
+ return err |
|
91 |
+ } |
|
92 |
+ return ioutil.WriteFile(dest, content, 0644) |
|
93 |
+} |