Skip to content

Commit b607fde

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

25 files changed

+2507
-2
lines changed

Diff for: .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 ./...

Diff for: .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:

Diff for: .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

Diff for: 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+
*/

Diff for: 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

Diff for: 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+
}

Diff for: 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+
}

Diff for: 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
}

Diff for: 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+
}

Diff for: 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 {

Diff for: 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 {

Diff for: pkg/backend/processor/model_config_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 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+
}

0 commit comments

Comments
 (0)