2017-02-27 19:12:13 +01:00
|
|
|
// Copyright 2017 Google Inc. All rights reserved.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package python
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2024-04-17 01:39:48 +02:00
|
|
|
"strings"
|
2017-02-27 19:12:13 +01:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"android/soong/android"
|
2023-01-24 20:48:08 +01:00
|
|
|
"android/soong/cc"
|
2024-04-17 01:39:48 +02:00
|
|
|
|
|
|
|
"github.com/google/blueprint"
|
2017-02-27 19:12:13 +01:00
|
|
|
)
|
|
|
|
|
2017-07-12 21:55:28 +02:00
|
|
|
type pyModule struct {
|
2017-12-18 22:20:23 +01:00
|
|
|
name string
|
|
|
|
actualVersion string
|
|
|
|
pyRunfiles []string
|
|
|
|
srcsZip string
|
|
|
|
depsSrcsZips []string
|
2017-02-27 19:12:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2023-01-24 20:48:08 +01:00
|
|
|
buildNamePrefix = "soong_python_test"
|
|
|
|
// We allow maching almost anything before the actual variant so that the os/arch variant
|
|
|
|
// is matched.
|
|
|
|
moduleVariantErrTemplate = `%s: module %q variant "[a-zA-Z0-9_]*%s": `
|
2017-02-27 19:12:13 +01:00
|
|
|
pkgPathErrTemplate = moduleVariantErrTemplate +
|
2017-07-12 21:55:28 +02:00
|
|
|
"pkg_path: %q must be a relative path contained in par file."
|
2017-02-27 19:12:13 +01:00
|
|
|
badIdentifierErrTemplate = moduleVariantErrTemplate +
|
2020-11-17 00:42:51 +01:00
|
|
|
"srcs: the path %q contains invalid subpath %q."
|
2017-02-27 19:12:13 +01:00
|
|
|
dupRunfileErrTemplate = moduleVariantErrTemplate +
|
2018-05-31 21:49:33 +02:00
|
|
|
"found two files to be placed at the same location within zip %q." +
|
2017-02-27 19:12:13 +01:00
|
|
|
" First file: in module %s at path %q." +
|
|
|
|
" Second file: in module %s at path %q."
|
|
|
|
noSrcFileErr = moduleVariantErrTemplate + "doesn't have any source files!"
|
2017-12-23 01:12:00 +01:00
|
|
|
badSrcFileExtErr = moduleVariantErrTemplate + "srcs: found non (.py|.proto) file: %q!"
|
2024-05-08 18:15:53 +02:00
|
|
|
badDataFileExtErr = moduleVariantErrTemplate + "data: found (.py) file: %q!"
|
2019-12-14 05:41:13 +01:00
|
|
|
bpFile = "Android.bp"
|
2017-02-27 19:12:13 +01:00
|
|
|
|
|
|
|
data = []struct {
|
|
|
|
desc string
|
2021-03-17 23:38:23 +01:00
|
|
|
mockFiles android.MockFS
|
2017-02-27 19:12:13 +01:00
|
|
|
|
|
|
|
errors []string
|
2017-07-12 21:55:28 +02:00
|
|
|
expectedBinaries []pyModule
|
2017-02-27 19:12:13 +01:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
desc: "module without any src files",
|
|
|
|
mockFiles: map[string][]byte{
|
|
|
|
filepath.Join("dir", bpFile): []byte(
|
|
|
|
`python_library_host {
|
|
|
|
name: "lib1",
|
|
|
|
}`,
|
|
|
|
),
|
|
|
|
},
|
|
|
|
errors: []string{
|
|
|
|
fmt.Sprintf(noSrcFileErr,
|
2019-12-14 05:41:13 +01:00
|
|
|
"dir/Android.bp:1:1", "lib1", "PY3"),
|
2017-02-27 19:12:13 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "module with bad src file ext",
|
|
|
|
mockFiles: map[string][]byte{
|
|
|
|
filepath.Join("dir", bpFile): []byte(
|
|
|
|
`python_library_host {
|
|
|
|
name: "lib1",
|
|
|
|
srcs: [
|
|
|
|
"file1.exe",
|
|
|
|
],
|
|
|
|
}`,
|
|
|
|
),
|
|
|
|
"dir/file1.exe": nil,
|
|
|
|
},
|
|
|
|
errors: []string{
|
|
|
|
fmt.Sprintf(badSrcFileExtErr,
|
2019-12-14 05:41:13 +01:00
|
|
|
"dir/Android.bp:3:11", "lib1", "PY3", "dir/file1.exe"),
|
2017-02-27 19:12:13 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "module with bad data file ext",
|
|
|
|
mockFiles: map[string][]byte{
|
|
|
|
filepath.Join("dir", bpFile): []byte(
|
|
|
|
`python_library_host {
|
|
|
|
name: "lib1",
|
|
|
|
srcs: [
|
|
|
|
"file1.py",
|
|
|
|
],
|
|
|
|
data: [
|
|
|
|
"file2.py",
|
|
|
|
],
|
|
|
|
}`,
|
|
|
|
),
|
|
|
|
"dir/file1.py": nil,
|
|
|
|
"dir/file2.py": nil,
|
|
|
|
},
|
|
|
|
errors: []string{
|
|
|
|
fmt.Sprintf(badDataFileExtErr,
|
2019-12-14 05:41:13 +01:00
|
|
|
"dir/Android.bp:6:11", "lib1", "PY3", "dir/file2.py"),
|
2017-02-27 19:12:13 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "module with bad pkg_path format",
|
|
|
|
mockFiles: map[string][]byte{
|
|
|
|
filepath.Join("dir", bpFile): []byte(
|
|
|
|
`python_library_host {
|
|
|
|
name: "lib1",
|
|
|
|
pkg_path: "a/c/../../",
|
|
|
|
srcs: [
|
|
|
|
"file1.py",
|
|
|
|
],
|
|
|
|
}
|
|
|
|
|
|
|
|
python_library_host {
|
|
|
|
name: "lib2",
|
|
|
|
pkg_path: "a/c/../../../",
|
|
|
|
srcs: [
|
|
|
|
"file1.py",
|
|
|
|
],
|
|
|
|
}
|
|
|
|
|
|
|
|
python_library_host {
|
|
|
|
name: "lib3",
|
|
|
|
pkg_path: "/a/c/../../",
|
|
|
|
srcs: [
|
|
|
|
"file1.py",
|
|
|
|
],
|
|
|
|
}`,
|
|
|
|
),
|
|
|
|
"dir/file1.py": nil,
|
|
|
|
},
|
|
|
|
errors: []string{
|
|
|
|
fmt.Sprintf(pkgPathErrTemplate,
|
2019-12-14 05:41:13 +01:00
|
|
|
"dir/Android.bp:11:15", "lib2", "PY3", "a/c/../../../"),
|
2017-02-27 19:12:13 +01:00
|
|
|
fmt.Sprintf(pkgPathErrTemplate,
|
2019-12-14 05:41:13 +01:00
|
|
|
"dir/Android.bp:19:15", "lib3", "PY3", "/a/c/../../"),
|
2017-02-27 19:12:13 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "module with bad runfile src path format",
|
|
|
|
mockFiles: map[string][]byte{
|
|
|
|
filepath.Join("dir", bpFile): []byte(
|
|
|
|
`python_library_host {
|
|
|
|
name: "lib1",
|
|
|
|
pkg_path: "a/b/c/",
|
|
|
|
srcs: [
|
|
|
|
".file1.py",
|
|
|
|
"123/file1.py",
|
|
|
|
"-e/f/file1.py",
|
|
|
|
],
|
|
|
|
}`,
|
|
|
|
),
|
|
|
|
"dir/.file1.py": nil,
|
|
|
|
"dir/123/file1.py": nil,
|
|
|
|
"dir/-e/f/file1.py": nil,
|
|
|
|
},
|
|
|
|
errors: []string{
|
2019-12-14 05:41:13 +01:00
|
|
|
fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11",
|
2018-05-31 21:49:33 +02:00
|
|
|
"lib1", "PY3", "a/b/c/-e/f/file1.py", "-e"),
|
2019-12-14 05:41:13 +01:00
|
|
|
fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11",
|
2018-05-31 21:49:33 +02:00
|
|
|
"lib1", "PY3", "a/b/c/.file1.py", ".file1"),
|
2019-12-14 05:41:13 +01:00
|
|
|
fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11",
|
2018-05-31 21:49:33 +02:00
|
|
|
"lib1", "PY3", "a/b/c/123/file1.py", "123"),
|
2017-02-27 19:12:13 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "module with duplicate runfile path",
|
|
|
|
mockFiles: map[string][]byte{
|
|
|
|
filepath.Join("dir", bpFile): []byte(
|
|
|
|
`python_library_host {
|
|
|
|
name: "lib1",
|
|
|
|
pkg_path: "a/b/",
|
|
|
|
srcs: [
|
|
|
|
"c/file1.py",
|
|
|
|
],
|
|
|
|
}
|
|
|
|
|
|
|
|
python_library_host {
|
|
|
|
name: "lib2",
|
|
|
|
pkg_path: "a/b/c/",
|
|
|
|
srcs: [
|
|
|
|
"file1.py",
|
|
|
|
],
|
|
|
|
libs: [
|
|
|
|
"lib1",
|
|
|
|
],
|
|
|
|
}
|
2021-03-18 00:01:06 +01:00
|
|
|
|
|
|
|
python_binary_host {
|
|
|
|
name: "bin",
|
|
|
|
pkg_path: "e/",
|
|
|
|
srcs: [
|
|
|
|
"bin.py",
|
|
|
|
],
|
|
|
|
libs: [
|
|
|
|
"lib2",
|
|
|
|
],
|
|
|
|
}
|
2017-02-27 19:12:13 +01:00
|
|
|
`,
|
|
|
|
),
|
|
|
|
"dir/c/file1.py": nil,
|
|
|
|
"dir/file1.py": nil,
|
2021-03-18 00:01:06 +01:00
|
|
|
"dir/bin.py": nil,
|
2017-02-27 19:12:13 +01:00
|
|
|
},
|
|
|
|
errors: []string{
|
2021-03-18 00:01:06 +01:00
|
|
|
fmt.Sprintf(dupRunfileErrTemplate, "dir/Android.bp:20:6",
|
|
|
|
"bin", "PY3", "a/b/c/file1.py", "bin", "dir/file1.py",
|
2017-02-27 19:12:13 +01:00
|
|
|
"lib1", "dir/c/file1.py"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "module for testing dependencies",
|
|
|
|
mockFiles: map[string][]byte{
|
|
|
|
filepath.Join("dir", bpFile): []byte(
|
2017-07-21 02:43:37 +02:00
|
|
|
`python_defaults {
|
|
|
|
name: "default_lib",
|
|
|
|
srcs: [
|
|
|
|
"default.py",
|
|
|
|
],
|
|
|
|
version: {
|
|
|
|
py2: {
|
|
|
|
enabled: true,
|
|
|
|
srcs: [
|
|
|
|
"default_py2.py",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
py3: {
|
|
|
|
enabled: false,
|
|
|
|
srcs: [
|
|
|
|
"default_py3.py",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
python_library_host {
|
2017-02-27 19:12:13 +01:00
|
|
|
name: "lib5",
|
|
|
|
pkg_path: "a/b/",
|
|
|
|
srcs: [
|
|
|
|
"file1.py",
|
|
|
|
],
|
|
|
|
version: {
|
|
|
|
py2: {
|
|
|
|
enabled: true,
|
|
|
|
},
|
|
|
|
py3: {
|
|
|
|
enabled: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
python_library_host {
|
|
|
|
name: "lib6",
|
|
|
|
pkg_path: "c/d/",
|
|
|
|
srcs: [
|
|
|
|
"file2.py",
|
|
|
|
],
|
|
|
|
libs: [
|
|
|
|
"lib5",
|
|
|
|
],
|
|
|
|
}
|
|
|
|
|
|
|
|
python_binary_host {
|
|
|
|
name: "bin",
|
2017-07-21 02:43:37 +02:00
|
|
|
defaults: ["default_lib"],
|
2017-02-27 19:12:13 +01:00
|
|
|
pkg_path: "e/",
|
|
|
|
srcs: [
|
|
|
|
"bin.py",
|
|
|
|
],
|
|
|
|
libs: [
|
|
|
|
"lib5",
|
|
|
|
],
|
|
|
|
version: {
|
|
|
|
py3: {
|
|
|
|
enabled: true,
|
|
|
|
srcs: [
|
|
|
|
"file4.py",
|
|
|
|
],
|
|
|
|
libs: [
|
|
|
|
"lib6",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}`,
|
|
|
|
),
|
2017-07-21 02:43:37 +02:00
|
|
|
filepath.Join("dir", "default.py"): nil,
|
|
|
|
filepath.Join("dir", "default_py2.py"): nil,
|
|
|
|
filepath.Join("dir", "default_py3.py"): nil,
|
|
|
|
filepath.Join("dir", "file1.py"): nil,
|
|
|
|
filepath.Join("dir", "file2.py"): nil,
|
|
|
|
filepath.Join("dir", "bin.py"): nil,
|
|
|
|
filepath.Join("dir", "file4.py"): nil,
|
2017-02-27 19:12:13 +01:00
|
|
|
},
|
2017-07-12 21:55:28 +02:00
|
|
|
expectedBinaries: []pyModule{
|
2017-02-27 19:12:13 +01:00
|
|
|
{
|
|
|
|
name: "bin",
|
|
|
|
actualVersion: "PY3",
|
|
|
|
pyRunfiles: []string{
|
2018-05-31 21:49:33 +02:00
|
|
|
"e/default.py",
|
|
|
|
"e/bin.py",
|
|
|
|
"e/default_py3.py",
|
|
|
|
"e/file4.py",
|
2017-02-27 19:12:13 +01:00
|
|
|
},
|
2021-03-17 23:38:23 +01:00
|
|
|
srcsZip: "out/soong/.intermediates/dir/bin/PY3/bin.py.srcszip",
|
2017-02-27 19:12:13 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestPythonModule(t *testing.T) {
|
|
|
|
for _, d := range data {
|
2021-03-17 23:38:23 +01:00
|
|
|
if d.desc != "module with duplicate runfile path" {
|
|
|
|
continue
|
|
|
|
}
|
2023-01-24 20:48:08 +01:00
|
|
|
d.mockFiles[filepath.Join("common", bpFile)] = []byte(`
|
|
|
|
python_library {
|
|
|
|
name: "py3-stdlib",
|
|
|
|
host_supported: true,
|
|
|
|
}
|
|
|
|
cc_binary {
|
|
|
|
name: "py3-launcher",
|
|
|
|
host_supported: true,
|
|
|
|
}
|
|
|
|
`)
|
2021-03-17 23:38:23 +01:00
|
|
|
|
2017-02-27 19:12:13 +01:00
|
|
|
t.Run(d.desc, func(t *testing.T) {
|
2021-03-20 01:36:55 +01:00
|
|
|
result := android.GroupFixturePreparers(
|
|
|
|
android.PrepareForTestWithDefaults,
|
2023-01-24 20:48:08 +01:00
|
|
|
android.PrepareForTestWithArchMutator,
|
|
|
|
android.PrepareForTestWithAllowMissingDependencies,
|
|
|
|
cc.PrepareForTestWithCcDefaultModules,
|
2021-03-20 01:36:55 +01:00
|
|
|
PrepareForTestWithPythonBuildComponents,
|
|
|
|
d.mockFiles.AddToFixture(),
|
2023-01-24 20:48:08 +01:00
|
|
|
).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(d.errors)).
|
2021-03-20 01:36:55 +01:00
|
|
|
RunTest(t)
|
2021-03-17 23:38:23 +01:00
|
|
|
|
|
|
|
if len(result.Errs) > 0 {
|
|
|
|
return
|
2017-02-27 19:12:13 +01:00
|
|
|
}
|
|
|
|
|
2021-03-17 23:38:23 +01:00
|
|
|
for _, e := range d.expectedBinaries {
|
|
|
|
t.Run(e.name, func(t *testing.T) {
|
2023-01-23 19:14:58 +01:00
|
|
|
expectModule(t, result.TestContext, e.name, e.actualVersion, e.srcsZip, e.pyRunfiles)
|
2021-03-17 23:38:23 +01:00
|
|
|
})
|
2017-02-27 19:12:13 +01:00
|
|
|
}
|
2021-03-17 23:38:23 +01:00
|
|
|
})
|
2017-02-27 19:12:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-17 01:39:48 +02:00
|
|
|
func TestTestOnlyProvider(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
ctx := android.GroupFixturePreparers(
|
|
|
|
PrepareForTestWithPythonBuildComponents,
|
|
|
|
android.PrepareForTestWithAllowMissingDependencies,
|
|
|
|
).RunTestWithBp(t, `
|
|
|
|
// These should be test-only
|
|
|
|
python_library { name: "py-lib-test", test_only: true }
|
|
|
|
python_library { name: "py-lib-test-host", test_only: true, host_supported: true }
|
|
|
|
python_test { name: "py-test", srcs: ["py-test.py"] }
|
|
|
|
python_test_host { name: "py-test-host", srcs: ["py-test-host.py"] }
|
|
|
|
python_binary_host { name: "py-bin-test", srcs: ["py-bin-test.py"] }
|
|
|
|
|
|
|
|
// These should not be.
|
|
|
|
python_library { name: "py-lib" }
|
|
|
|
python_binary_host { name: "py-bin", srcs: ["py-bin.py"] }
|
|
|
|
`)
|
|
|
|
|
|
|
|
// Visit all modules and ensure only the ones that should
|
|
|
|
// marked as test-only are marked as test-only.
|
|
|
|
|
|
|
|
actualTestOnly := []string{}
|
|
|
|
ctx.VisitAllModules(func(m blueprint.Module) {
|
|
|
|
if provider, ok := android.OtherModuleProvider(ctx.TestContext.OtherModuleProviderAdaptor(), m, android.TestOnlyProviderKey); ok {
|
|
|
|
if provider.TestOnly {
|
|
|
|
actualTestOnly = append(actualTestOnly, m.Name())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
expectedTestOnlyModules := []string{
|
|
|
|
"py-lib-test",
|
|
|
|
"py-lib-test-host",
|
|
|
|
"py-test",
|
|
|
|
"py-test-host",
|
|
|
|
}
|
|
|
|
|
|
|
|
notEqual, left, right := android.ListSetDifference(expectedTestOnlyModules, actualTestOnly)
|
|
|
|
if notEqual {
|
|
|
|
t.Errorf("test-only: Expected but not found: %v, Found but not expected: %v", left, right)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't allow setting test-only on things that are always tests or never tests.
|
|
|
|
func TestInvalidTestOnlyTargets(t *testing.T) {
|
|
|
|
testCases := []string{
|
|
|
|
` python_test { name: "py-test", test_only: true, srcs: ["py-test.py"] } `,
|
|
|
|
` python_test_host { name: "py-test-host", test_only: true, srcs: ["py-test-host.py"] } `,
|
|
|
|
` python_defaults { name: "py-defaults", test_only: true, srcs: ["foo.py"] } `,
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, bp := range testCases {
|
|
|
|
ctx := android.GroupFixturePreparers(
|
|
|
|
PrepareForTestWithPythonBuildComponents,
|
|
|
|
android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
|
|
|
|
|
|
|
|
ctx.RegisterModuleType("python_defaults", DefaultsFactory)
|
|
|
|
}),
|
|
|
|
android.PrepareForTestWithAllowMissingDependencies).
|
|
|
|
ExtendWithErrorHandler(android.FixtureIgnoreErrors).
|
|
|
|
RunTestWithBp(t, bp)
|
|
|
|
if len(ctx.Errs) != 1 {
|
|
|
|
t.Errorf("Expected err setting test_only in testcase #%d: %d errs", i, len(ctx.Errs))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !strings.Contains(ctx.Errs[0].Error(), "unrecognized property \"test_only\"") {
|
|
|
|
t.Errorf("ERR: %s bad bp: %s", ctx.Errs[0], bp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-23 19:14:58 +01:00
|
|
|
func expectModule(t *testing.T, ctx *android.TestContext, name, variant, expectedSrcsZip string, expectedPyRunfiles []string) {
|
2017-07-13 23:43:27 +02:00
|
|
|
module := ctx.ModuleForTests(name, variant)
|
2017-02-27 19:12:13 +01:00
|
|
|
|
2023-01-23 19:14:58 +01:00
|
|
|
base, baseOk := module.Module().(*PythonLibraryModule)
|
2017-02-27 19:12:13 +01:00
|
|
|
if !baseOk {
|
|
|
|
t.Fatalf("%s is not Python module!", name)
|
|
|
|
}
|
|
|
|
|
2017-12-18 22:20:23 +01:00
|
|
|
actualPyRunfiles := []string{}
|
2017-02-27 19:12:13 +01:00
|
|
|
for _, path := range base.srcsPathMappings {
|
2017-12-18 22:20:23 +01:00
|
|
|
actualPyRunfiles = append(actualPyRunfiles, path.dest)
|
2017-02-27 19:12:13 +01:00
|
|
|
}
|
|
|
|
|
2021-03-17 23:38:23 +01:00
|
|
|
android.AssertDeepEquals(t, "pyRunfiles", expectedPyRunfiles, actualPyRunfiles)
|
2017-02-27 19:12:13 +01:00
|
|
|
|
2021-03-17 23:38:23 +01:00
|
|
|
android.AssertPathRelativeToTopEquals(t, "srcsZip", expectedSrcsZip, base.srcsZip)
|
2017-02-27 19:12:13 +01:00
|
|
|
}
|
|
|
|
|
2019-12-14 05:41:13 +01:00
|
|
|
func TestMain(m *testing.M) {
|
2021-03-17 23:38:23 +01:00
|
|
|
os.Exit(m.Run())
|
2019-12-14 05:41:13 +01:00
|
|
|
}
|