Skip to content

Commit b607fde

Browse files
authoredOct 31, 2024··
feat: add unit tests (#15)
Signed-off-by: chlins <[email protected]>
1 parent 668fcf2 commit b607fde

25 files changed

+2507
-2
lines changed
 

‎.github/workflows/ci.yaml

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on:
1010
schedule:
1111
- cron: '0 4 * * *'
1212

13-
permissions:
13+
permissions:
1414
contents: read
1515

1616
jobs:
@@ -31,4 +31,5 @@ jobs:
3131

3232
- name: Run Unit tests
3333
run: |-
34-
echo running unit tests
34+
go version
35+
go test -v ./...

‎.golangci.yml

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
run:
22
deadline: 3m
33
modules-download-mode: readonly
4+
skip-dirs:
5+
- test/mocks
6+
47

58
linters-settings:
69
gocyclo:

‎.mockery.yaml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
with-expecter: true
2+
boilerplate-file: copyright.txt
3+
outpkg: "{{.PackageName}}"
4+
mockname: "{{.InterfaceName}}"
5+
filename: "{{.InterfaceName | snakecase}}.go"
6+
packages:
7+
github.com/CloudNativeAI/modctl/pkg/backend:
8+
interfaces:
9+
Backend:
10+
config:
11+
dir: test/mocks/backend
12+
github.com/CloudNativeAI/modctl/pkg/storage:
13+
interfaces:
14+
Storage:
15+
config:
16+
dir: test/mocks/storage
17+
github.com/CloudNativeAI/modctl/pkg/modelfile:
18+
interfaces:
19+
Modelfile:
20+
config:
21+
dir: test/mocks/modelfile

‎copyright.txt

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright 2024 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/

‎go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ require (
8383
github.com/spf13/afero v1.11.0 // indirect
8484
github.com/spf13/cast v1.7.0 // indirect
8585
github.com/spf13/pflag v1.0.5 // indirect
86+
github.com/stretchr/objx v0.5.2 // indirect
8687
github.com/subosito/gotenv v1.6.0 // indirect
8788
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
8889
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect

‎pkg/backend/build_test.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2024 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package backend
18+
19+
import (
20+
"testing"
21+
"time"
22+
23+
modelspec "github.com/CloudNativeAI/modctl/pkg/spec"
24+
"github.com/CloudNativeAI/modctl/test/mocks/modelfile"
25+
26+
"github.com/stretchr/testify/assert"
27+
)
28+
29+
func TestManifestAnnotation(t *testing.T) {
30+
modelfile := &modelfile.Modelfile{}
31+
modelfile.On("GetArch").Return("test-arch")
32+
modelfile.On("GetFamily").Return("test-family")
33+
modelfile.On("GetName").Return("test-model")
34+
modelfile.On("GetFormat").Return("test-format")
35+
modelfile.On("GetParamsize").Return("12345")
36+
modelfile.On("GetPrecision").Return("FP32")
37+
modelfile.On("GetQuantization").Return("INT8")
38+
39+
annotations := manifestAnnotation(modelfile)
40+
41+
assert.Equal(t, "test-arch", annotations[modelspec.AnnotationArchitecture])
42+
assert.Equal(t, "test-family", annotations[modelspec.AnnotationFamily])
43+
assert.Equal(t, "test-model", annotations[modelspec.AnnotationName])
44+
assert.Equal(t, "test-format", annotations[modelspec.AnnotationFormat])
45+
assert.Equal(t, "12345", annotations[modelspec.AnnotationParamSize])
46+
assert.Equal(t, "FP32", annotations[modelspec.AnnotationPrecision])
47+
assert.Equal(t, "INT8", annotations[modelspec.AnnotationQuantization])
48+
49+
createdTime, err := time.Parse(time.RFC3339, annotations[modelspec.AnnotationCreated])
50+
assert.NoError(t, err)
51+
assert.WithinDuration(t, time.Now(), createdTime, time.Minute)
52+
}
53+
54+
func TestGetProcessors(t *testing.T) {
55+
modelfile := &modelfile.Modelfile{}
56+
modelfile.On("GetConfigs").Return([]string{"config1", "config2"})
57+
modelfile.On("GetModels").Return([]string{"model1", "model2"})
58+
59+
processors := getProcessors(modelfile)
60+
61+
assert.Len(t, processors, 4)
62+
assert.Equal(t, "license", processors[0].Name())
63+
assert.Equal(t, "readme", processors[1].Name())
64+
assert.Equal(t, "model_config", processors[2].Name())
65+
assert.Equal(t, "model", processors[3].Name())
66+
}

‎pkg/backend/list_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2024 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package backend
18+
19+
import (
20+
"context"
21+
"encoding/json"
22+
"testing"
23+
"time"
24+
25+
"github.com/CloudNativeAI/modctl/test/mocks/storage"
26+
"github.com/stretchr/testify/assert"
27+
"github.com/stretchr/testify/mock"
28+
29+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
30+
)
31+
32+
func TestList(t *testing.T) {
33+
mockStore := &storage.Storage{}
34+
b := &backend{store: mockStore}
35+
ctx := context.Background()
36+
repos := []string{"example.com/repo1", "example.com/repo2"}
37+
tags := []string{"tag1", "tag2"}
38+
createdAt := time.Now().Format(time.RFC3339)
39+
manifest := ocispec.Manifest{
40+
Layers: []ocispec.Descriptor{
41+
{Size: 1024},
42+
{Size: 1024},
43+
},
44+
Config: ocispec.Descriptor{Size: 1024},
45+
Annotations: map[string]string{
46+
"org.cnai.model.created": createdAt,
47+
},
48+
}
49+
manifestRaw, err := json.Marshal(manifest)
50+
assert.NoError(t, err)
51+
52+
mockStore.On("ListRepositories", ctx).Return(repos, nil)
53+
mockStore.On("ListTags", ctx, repos[0]).Return(tags, nil)
54+
mockStore.On("ListTags", ctx, repos[1]).Return(tags, nil)
55+
mockStore.On("PullManifest", ctx, mock.Anything, mock.Anything).Return(manifestRaw, "sha256:1234567890abcdef", nil)
56+
57+
artifacts, err := b.List(ctx)
58+
assert.NoError(t, err, "list failed")
59+
assert.Len(t, artifacts, 4, "unexpected number of artifacts")
60+
assert.Equal(t, repos[0], artifacts[0].Repository, "unexpected repository")
61+
assert.Equal(t, tags[0], artifacts[0].Tag, "unexpected tag")
62+
assert.Equal(t, "sha256:1234567890abcdef", artifacts[0].Digest, "unexpected digest")
63+
assert.Equal(t, int64(3*1024+len(manifestRaw)), artifacts[0].Size, "unexpected size")
64+
assert.Equal(t, createdAt, artifacts[0].CreatedAt.Format(time.RFC3339), "unexpected created at")
65+
}

‎pkg/backend/processor/license.go

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ func NewLicenseProcessor() Processor {
3535
// licenseProcessor is the processor to process the LICENSE file.
3636
type licenseProcessor struct{}
3737

38+
func (p *licenseProcessor) Name() string {
39+
return "license"
40+
}
41+
3842
func (p *licenseProcessor) Identify(_ context.Context, path string, info os.FileInfo) bool {
3943
return info.Name() == "LICENSE" || info.Name() == "LICENSE.txt"
4044
}

‎pkg/backend/processor/license_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2024 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package processor
18+
19+
import (
20+
"context"
21+
"testing"
22+
"testing/fstest"
23+
24+
modelspec "github.com/CloudNativeAI/modctl/pkg/spec"
25+
"github.com/CloudNativeAI/modctl/test/mocks/storage"
26+
27+
"github.com/stretchr/testify/assert"
28+
"github.com/stretchr/testify/mock"
29+
)
30+
31+
func TestLicenseProcessor_Name(t *testing.T) {
32+
p := NewLicenseProcessor()
33+
assert.Equal(t, "license", p.Name())
34+
}
35+
36+
func TestLicenseProcessor_Identify(t *testing.T) {
37+
p := NewLicenseProcessor()
38+
mockFS := fstest.MapFS{
39+
"LICENSE": &fstest.MapFile{},
40+
"LICENSE.txt": &fstest.MapFile{},
41+
"README.md": &fstest.MapFile{},
42+
}
43+
info, err := mockFS.Stat("LICENSE")
44+
assert.NoError(t, err)
45+
assert.True(t, p.Identify(context.Background(), "LICENSE", info))
46+
47+
info, err = mockFS.Stat("LICENSE.txt")
48+
assert.NoError(t, err)
49+
assert.True(t, p.Identify(context.Background(), "LICENSE.txt", info))
50+
51+
info, err = mockFS.Stat("README.md")
52+
assert.NoError(t, err)
53+
assert.False(t, p.Identify(context.Background(), "README.md", info))
54+
}
55+
56+
func TestLicenseProcessor_Process(t *testing.T) {
57+
p := NewLicenseProcessor()
58+
ctx := context.Background()
59+
mockStore := &storage.Storage{}
60+
repo := "test-repo"
61+
path := "LICENSE"
62+
mockFS := fstest.MapFS{
63+
"LICENSE": &fstest.MapFile{},
64+
}
65+
info, err := mockFS.Stat("LICENSE")
66+
assert.NoError(t, err)
67+
68+
mockStore.On("PushBlob", ctx, repo, mock.Anything).Return("sha256:1234567890abcdef", int64(1024), nil)
69+
70+
desc, err := p.Process(ctx, mockStore, repo, path, info)
71+
assert.NoError(t, err)
72+
assert.NotNil(t, desc)
73+
assert.Equal(t, "sha256:1234567890abcdef", desc.Digest.String())
74+
assert.Equal(t, int64(1024), desc.Size)
75+
assert.Equal(t, "true", desc.Annotations[modelspec.AnnotationLicense])
76+
}

‎pkg/backend/processor/model.go

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ type modelProcessor struct {
4141
models []string
4242
}
4343

44+
func (p *modelProcessor) Name() string {
45+
return "model"
46+
}
47+
4448
func (p *modelProcessor) Identify(_ context.Context, path string, info os.FileInfo) bool {
4549
for _, model := range p.models {
4650
if matched, _ := regexp.MatchString(model, info.Name()); matched {

‎pkg/backend/processor/model_config.go

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ type modelConfigProcessor struct {
4141
configs []string
4242
}
4343

44+
func (p *modelConfigProcessor) Name() string {
45+
return "model_config"
46+
}
47+
4448
func (p *modelConfigProcessor) Identify(_ context.Context, path string, info os.FileInfo) bool {
4549
for _, config := range p.configs {
4650
if matched, _ := regexp.MatchString(config, info.Name()); matched {
+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2024 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package processor
18+
19+
import (
20+
"context"
21+
"testing"
22+
"testing/fstest"
23+
24+
modelspec "github.com/CloudNativeAI/modctl/pkg/spec"
25+
"github.com/CloudNativeAI/modctl/test/mocks/storage"
26+
27+
"github.com/stretchr/testify/assert"
28+
"github.com/stretchr/testify/mock"
29+
)
30+
31+
func TestModelConfigProcessor_Name(t *testing.T) {
32+
p := NewModelConfigProcessor([]string{"config*"})
33+
assert.Equal(t, "model_config", p.Name())
34+
}
35+
36+
func TestModelConfigProcessor_Identify(t *testing.T) {
37+
p := NewModelConfigProcessor([]string{"config*"})
38+
mockFS := fstest.MapFS{
39+
"config-1": &fstest.MapFile{},
40+
"config-2": &fstest.MapFile{},
41+
"model": &fstest.MapFile{},
42+
}
43+
info, err := mockFS.Stat("config-1")
44+
assert.NoError(t, err)
45+
assert.True(t, p.Identify(context.Background(), "config-1", info))
46+
47+
info, err = mockFS.Stat("config-2")
48+
assert.NoError(t, err)
49+
assert.True(t, p.Identify(context.Background(), "config-2", info))
50+
51+
info, err = mockFS.Stat("model")
52+
assert.NoError(t, err)
53+
assert.False(t, p.Identify(context.Background(), "model", info))
54+
}
55+
56+
func TestModelConfigProcessor_Process(t *testing.T) {
57+
p := NewModelConfigProcessor([]string{"config*"})
58+
ctx := context.Background()
59+
mockStore := &storage.Storage{}
60+
repo := "test-repo"
61+
path := "config"
62+
mockFS := fstest.MapFS{
63+
"config": &fstest.MapFile{},
64+
}
65+
info, err := mockFS.Stat("config")
66+
assert.NoError(t, err)
67+
68+
mockStore.On("PushBlob", ctx, repo, mock.Anything).Return("sha256:1234567890abcdef", int64(1024), nil)
69+
70+
desc, err := p.Process(ctx, mockStore, repo, path, info)
71+
assert.NoError(t, err)
72+
assert.NotNil(t, desc)
73+
assert.Equal(t, "sha256:1234567890abcdef", desc.Digest.String())
74+
assert.Equal(t, int64(1024), desc.Size)
75+
assert.Equal(t, "true", desc.Annotations[modelspec.AnnotationConfig])
76+
}

‎pkg/backend/processor/model_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2024 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package processor
18+
19+
import (
20+
"context"
21+
"testing"
22+
"testing/fstest"
23+
24+
modelspec "github.com/CloudNativeAI/modctl/pkg/spec"
25+
"github.com/CloudNativeAI/modctl/test/mocks/storage"
26+
27+
"github.com/stretchr/testify/assert"
28+
"github.com/stretchr/testify/mock"
29+
)
30+
31+
func TestModelProcessor_Name(t *testing.T) {
32+
p := NewModelProcessor([]string{"model*"})
33+
assert.Equal(t, "model", p.Name())
34+
}
35+
36+
func TestModelProcessor_Identify(t *testing.T) {
37+
p := NewModelProcessor([]string{"model*"})
38+
mockFS := fstest.MapFS{
39+
"model-1": &fstest.MapFile{},
40+
"model-2": &fstest.MapFile{},
41+
"config": &fstest.MapFile{},
42+
}
43+
info, err := mockFS.Stat("model-1")
44+
assert.NoError(t, err)
45+
assert.True(t, p.Identify(context.Background(), "model-1", info))
46+
47+
info, err = mockFS.Stat("model-2")
48+
assert.NoError(t, err)
49+
assert.True(t, p.Identify(context.Background(), "model-2", info))
50+
51+
info, err = mockFS.Stat("config")
52+
assert.NoError(t, err)
53+
assert.False(t, p.Identify(context.Background(), "config", info))
54+
}
55+
56+
func TestModelProcessor_Process(t *testing.T) {
57+
p := NewModelProcessor([]string{"model*"})
58+
ctx := context.Background()
59+
mockStore := &storage.Storage{}
60+
repo := "test-repo"
61+
path := "model"
62+
mockFS := fstest.MapFS{
63+
"model": &fstest.MapFile{},
64+
}
65+
info, err := mockFS.Stat("model")
66+
assert.NoError(t, err)
67+
68+
mockStore.On("PushBlob", ctx, repo, mock.Anything).Return("sha256:1234567890abcdef", int64(1024), nil)
69+
70+
desc, err := p.Process(ctx, mockStore, repo, path, info)
71+
assert.NoError(t, err)
72+
assert.NotNil(t, desc)
73+
assert.Equal(t, "sha256:1234567890abcdef", desc.Digest.String())
74+
assert.Equal(t, int64(1024), desc.Size)
75+
assert.Equal(t, "true", desc.Annotations[modelspec.AnnotationModel])
76+
}

‎pkg/backend/processor/processor.go

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import (
2727

2828
// Processor is the interface to recognize and process the identified file.
2929
type Processor interface {
30+
// Name returns the name of the processor.
31+
Name() string
3032
// Identify identifies the file, returns true if the file is identified,
3133
// then the Process will be called to process the file, otherwise it will be skipped.
3234
Identify(ctx context.Context, path string, info os.FileInfo) bool

‎pkg/backend/processor/readme.go

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ func NewReadmeProcessor() Processor {
3535
// readmeProcessor is the processor to process the README file.
3636
type readmeProcessor struct{}
3737

38+
func (p *readmeProcessor) Name() string {
39+
return "readme"
40+
}
41+
3842
func (p *readmeProcessor) Identify(_ context.Context, path string, info os.FileInfo) bool {
3943
return info.Name() == "README.md" || info.Name() == "README"
4044
}

‎pkg/backend/processor/readme_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2024 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package processor
18+
19+
import (
20+
"context"
21+
"testing"
22+
"testing/fstest"
23+
24+
modelspec "github.com/CloudNativeAI/modctl/pkg/spec"
25+
"github.com/CloudNativeAI/modctl/test/mocks/storage"
26+
27+
"github.com/stretchr/testify/assert"
28+
"github.com/stretchr/testify/mock"
29+
)
30+
31+
func TestReadmeProcessor_Name(t *testing.T) {
32+
p := NewReadmeProcessor()
33+
assert.Equal(t, "readme", p.Name())
34+
}
35+
36+
func TestReadmeProcessor_Identify(t *testing.T) {
37+
p := NewReadmeProcessor()
38+
mockFS := fstest.MapFS{
39+
"README": &fstest.MapFile{},
40+
"README.md": &fstest.MapFile{},
41+
"LICENSE": &fstest.MapFile{},
42+
}
43+
info, err := mockFS.Stat("README")
44+
assert.NoError(t, err)
45+
assert.True(t, p.Identify(context.Background(), "README", info))
46+
47+
info, err = mockFS.Stat("README.md")
48+
assert.NoError(t, err)
49+
assert.True(t, p.Identify(context.Background(), "README.md", info))
50+
51+
info, err = mockFS.Stat("LICENSE")
52+
assert.NoError(t, err)
53+
assert.False(t, p.Identify(context.Background(), "LICENSE", info))
54+
}
55+
56+
func TestReadmeProcessor_Process(t *testing.T) {
57+
p := NewReadmeProcessor()
58+
ctx := context.Background()
59+
mockStore := &storage.Storage{}
60+
repo := "test-repo"
61+
path := "README"
62+
mockFS := fstest.MapFS{
63+
"README": &fstest.MapFile{},
64+
}
65+
info, err := mockFS.Stat("README")
66+
assert.NoError(t, err)
67+
68+
mockStore.On("PushBlob", ctx, repo, mock.Anything).Return("sha256:1234567890abcdef", int64(1024), nil)
69+
70+
desc, err := p.Process(ctx, mockStore, repo, path, info)
71+
assert.NoError(t, err)
72+
assert.NotNil(t, desc)
73+
assert.Equal(t, "sha256:1234567890abcdef", desc.Digest.String())
74+
assert.Equal(t, int64(1024), desc.Size)
75+
assert.Equal(t, "true", desc.Annotations[modelspec.AnnotationReadme])
76+
}

‎pkg/backend/prune_test.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2024 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package backend
18+
19+
import (
20+
"context"
21+
"encoding/json"
22+
"testing"
23+
24+
"github.com/CloudNativeAI/modctl/test/mocks/storage"
25+
26+
godigest "github.com/opencontainers/go-digest"
27+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
28+
"github.com/stretchr/testify/assert"
29+
)
30+
31+
func TestPrune(t *testing.T) {
32+
mockStore := &storage.Storage{}
33+
b := &backend{store: mockStore}
34+
ctx := context.Background()
35+
repos := []string{"example.com/repo1", "example.com/repo2"}
36+
37+
mustToJson := func(v any) []byte {
38+
jsonByte, err := json.Marshal(v)
39+
assert.NoError(t, err)
40+
return jsonByte
41+
}
42+
43+
indexs := []ocispec.Index{
44+
{
45+
Manifests: []ocispec.Descriptor{
46+
{Digest: godigest.Digest("sha256:1234567890abcdef-repo1")},
47+
},
48+
},
49+
{
50+
Manifests: []ocispec.Descriptor{
51+
{Digest: godigest.Digest("sha256:1234567890abcdef-repo2")},
52+
},
53+
},
54+
}
55+
manifests := []ocispec.Manifest{
56+
{Layers: []ocispec.Descriptor{{Digest: godigest.Digest("sha256:1234567890abcdef-repo1-blob1")}}},
57+
{Layers: []ocispec.Descriptor{{Digest: godigest.Digest("sha256:1234567890abcdef-repo2-blob1")}}},
58+
}
59+
blobs := []string{
60+
"sha256:1234567890abcdef-repo1-blob1",
61+
"sha256:1234567890abcdef-repo1-blob2",
62+
"sha256:1234567890abcdef-repo2-blob1",
63+
"sha256:1234567890abcdef-repo2-blob2",
64+
}
65+
66+
mockStore.On("ListRepositories", ctx).Return(repos, nil)
67+
mockStore.On("GetIndex", ctx, repos[0]).Return(mustToJson(indexs[0]), nil)
68+
mockStore.On("GetIndex", ctx, repos[1]).Return(mustToJson(indexs[1]), nil)
69+
mockStore.On("PullManifest", ctx, repos[0], indexs[0].Manifests[0].Digest.String()).Return(mustToJson(manifests[0]), indexs[0].Manifests[0].Digest.String(), nil)
70+
mockStore.On("PullManifest", ctx, repos[1], indexs[1].Manifests[0].Digest.String()).Return(mustToJson(manifests[1]), indexs[1].Manifests[0].Digest.String(), nil)
71+
mockStore.On("ListBlobs", ctx, repos[0]).Return([]string{blobs[0], blobs[1]}, nil)
72+
mockStore.On("ListBlobs", ctx, repos[1]).Return([]string{blobs[2], blobs[3]}, nil)
73+
mockStore.On("CleanupRepo", ctx, repos[0], []string{blobs[1]}, false).Return(1, nil)
74+
mockStore.On("CleanupRepo", ctx, repos[1], []string{blobs[3]}, false).Return(1, nil)
75+
76+
prunedBlobs, err := b.Prune(ctx)
77+
assert.NoError(t, err)
78+
assert.Len(t, prunedBlobs, 2, "pruned blobs length is not equal to expected length")
79+
assert.Equal(t, repos[0]+"@"+blobs[1], prunedBlobs[0], "pruned blob is not equal to expected blob")
80+
assert.Equal(t, repos[1]+"@"+blobs[3], prunedBlobs[1], "pruned blob is not equal to expected blob")
81+
}

‎pkg/backend/pull_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2024 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package backend_test
18+
19+
import (
20+
"context"
21+
"errors"
22+
"testing"
23+
24+
mocks "github.com/CloudNativeAI/modctl/test/mocks/backend"
25+
26+
"github.com/stretchr/testify/assert"
27+
)
28+
29+
func TestPull(t *testing.T) {
30+
ctx := context.Background()
31+
target1 := "example.com/test-repo:should_error"
32+
target2 := "example.com/test-repo:should_not_error"
33+
34+
b := &mocks.Backend{}
35+
b.On("Pull", ctx, target1).Return(errors.New("mock error"))
36+
err := b.Pull(ctx, target1)
37+
assert.Error(t, err, "Push should return an error")
38+
39+
b.On("Pull", ctx, target2).Return(nil)
40+
err = b.Pull(ctx, target2)
41+
assert.NoError(t, err, "Push should not return an error")
42+
}

‎pkg/backend/push_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2024 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package backend_test
18+
19+
import (
20+
"context"
21+
"errors"
22+
"testing"
23+
24+
mocks "github.com/CloudNativeAI/modctl/test/mocks/backend"
25+
26+
"github.com/stretchr/testify/assert"
27+
)
28+
29+
func TestPush(t *testing.T) {
30+
ctx := context.Background()
31+
target1 := "example.com/test-repo:should_error"
32+
target2 := "example.com/test-repo:should_not_error"
33+
34+
b := &mocks.Backend{}
35+
b.On("Push", ctx, target1).Return(errors.New("mock error"))
36+
err := b.Push(ctx, target1)
37+
assert.Error(t, err, "Push should return an error")
38+
39+
b.On("Push", ctx, target2).Return(nil)
40+
err = b.Push(ctx, target2)
41+
assert.NoError(t, err, "Push should not return an error")
42+
}

‎pkg/backend/rm_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2024 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package backend
18+
19+
import (
20+
"context"
21+
"testing"
22+
23+
"github.com/CloudNativeAI/modctl/test/mocks/storage"
24+
25+
"github.com/stretchr/testify/assert"
26+
)
27+
28+
func TestRemove(t *testing.T) {
29+
mockStore := &storage.Storage{}
30+
b := &backend{store: mockStore}
31+
ctx := context.Background()
32+
target := "example.com/repo:tag"
33+
ref, err := ParseReference("example.com/repo:tag")
34+
assert.NoError(t, err)
35+
digest := "sha256:1234567890abcdef"
36+
37+
mockStore.On("PullManifest", ctx, ref.Repository(), ref.Tag()).Return(nil, digest, nil)
38+
mockStore.On("DeleteManifest", ctx, ref.Repository(), digest).Return(nil)
39+
40+
result, err := b.Remove(ctx, target)
41+
assert.NoError(t, err)
42+
assert.Equal(t, digest, result)
43+
44+
mockStore.AssertExpectations(t)
45+
}

‎pkg/config/build_test.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2024 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package config
18+
19+
import (
20+
"testing"
21+
)
22+
23+
func TestNewBuild(t *testing.T) {
24+
build := NewBuild()
25+
if build.Target != "" {
26+
t.Errorf("expected Target to be empty, got %s", build.Target)
27+
}
28+
if build.Modelfile != "Modelfile" {
29+
t.Errorf("expected Modelfile to be 'Modelfile', got %s", build.Modelfile)
30+
}
31+
}
32+
33+
func TestBuild_Validate(t *testing.T) {
34+
tests := []struct {
35+
name string
36+
build *Build
37+
expectErr bool
38+
}{
39+
{
40+
name: "valid build",
41+
build: &Build{
42+
Target: "target",
43+
Modelfile: "Modelfile",
44+
},
45+
expectErr: false,
46+
},
47+
{
48+
name: "missing target",
49+
build: &Build{
50+
Target: "",
51+
Modelfile: "Modelfile",
52+
},
53+
expectErr: true,
54+
},
55+
{
56+
name: "missing modelfile",
57+
build: &Build{
58+
Target: "target",
59+
Modelfile: "",
60+
},
61+
expectErr: true,
62+
},
63+
}
64+
65+
for _, tt := range tests {
66+
t.Run(tt.name, func(t *testing.T) {
67+
err := tt.build.Validate()
68+
if (err != nil) != tt.expectErr {
69+
t.Errorf("expected error: %v, got: %v", tt.expectErr, err)
70+
}
71+
})
72+
}
73+
}

‎pkg/config/login_test.go

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2024 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package config
18+
19+
import (
20+
"testing"
21+
)
22+
23+
func TestNewLogin(t *testing.T) {
24+
login := NewLogin()
25+
if login.Username != "" {
26+
t.Errorf("expected empty username, got %s", login.Username)
27+
}
28+
if login.Password != "" {
29+
t.Errorf("expected empty password, got %s", login.Password)
30+
}
31+
if login.PasswordStdin != false {
32+
t.Errorf("expected PasswordStdin to be false, got %v", login.PasswordStdin)
33+
}
34+
}
35+
36+
func TestLogin_Validate(t *testing.T) {
37+
tests := []struct {
38+
name string
39+
login *Login
40+
wantErr bool
41+
errMsg string
42+
}{
43+
{
44+
name: "missing username",
45+
login: &Login{
46+
Username: "",
47+
Password: "password",
48+
},
49+
wantErr: true,
50+
errMsg: "missing username",
51+
},
52+
{
53+
name: "missing password",
54+
login: &Login{
55+
Username: "username",
56+
Password: "",
57+
},
58+
wantErr: true,
59+
errMsg: "missing password",
60+
},
61+
{
62+
name: "valid login",
63+
login: &Login{
64+
Username: "username",
65+
Password: "password",
66+
},
67+
wantErr: false,
68+
},
69+
}
70+
71+
for _, tt := range tests {
72+
t.Run(tt.name, func(t *testing.T) {
73+
err := tt.login.Validate()
74+
if (err != nil) != tt.wantErr {
75+
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
76+
return
77+
}
78+
if err != nil && err.Error() != tt.errMsg {
79+
t.Errorf("Validate() error message = %v, want %v", err.Error(), tt.errMsg)
80+
}
81+
})
82+
}
83+
}

‎test/mocks/backend/backend.go

+496
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎test/mocks/modelfile/modelfile.go

+457
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎test/mocks/storage/storage.go

+692
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.