Skip to content

Commit

Permalink
feat: add API for checker and support mod checking in update (#557)
Browse files Browse the repository at this point in the history
* feat: add API for checker and support mod checking in update

Signed-off-by: zongz <zongzhe1024@163.com>

* fix: fix test cases

Signed-off-by: zongz <zongzhe1024@163.com>

* fix: fix test cases

Signed-off-by: zongz <zongzhe1024@163.com>

---------

Signed-off-by: zongz <zongzhe1024@163.com>
  • Loading branch information
zong-zhe authored Nov 29, 2024
1 parent eaea0d4 commit 870a418
Show file tree
Hide file tree
Showing 21 changed files with 375 additions and 115 deletions.
69 changes: 41 additions & 28 deletions pkg/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,40 +25,57 @@ type Checker interface {
Check(pkg.KclPkg) error
}

// DepChecker is responsible for running multiple checkers on a package's dependencies.
type DepChecker struct {
// ModChecker is responsible for running multiple checkers on a KCL module.
type ModChecker struct {
checkers []Checker
}

// DepCheckerOption configures how we set up DepChecker.
type DepCheckerOption func(*DepChecker)
// ModCheckerOption configures how we set up ModChecker.
type ModCheckerOption func(*ModChecker)

// NewDepChecker creates a new DepChecker with options.
func NewDepChecker(options ...DepCheckerOption) *DepChecker {
depChecker := &DepChecker{}
// NewModChecker creates a new ModChecker with options.
func NewModChecker(options ...ModCheckerOption) *ModChecker {
ModChecker := &ModChecker{}
for _, opt := range options {
opt(depChecker)
opt(ModChecker)
}
return depChecker
return ModChecker
}

// WithChecker adds a single Checker to DepChecker.
func WithChecker(checker Checker) DepCheckerOption {
return func(c *DepChecker) {
// WithChecker adds a single Checker to ModChecker.
func WithChecker(checker Checker) ModCheckerOption {
return func(c *ModChecker) {
if c.checkers == nil {
c.checkers = []Checker{}
}
c.checkers = append(c.checkers, checker)
}
}

// WithCheckers adds multiple Checkers to DepChecker.
func WithCheckers(checkers ...Checker) DepCheckerOption {
return func(c *DepChecker) {
// WithCheckers adds multiple Checkers to ModChecker.
func WithCheckers(checkers ...Checker) ModCheckerOption {
return func(c *ModChecker) {
if c.checkers == nil {
c.checkers = []Checker{}
}
c.checkers = append(c.checkers, checkers...)
}
}

func (mc *ModChecker) AddChecker(checker Checker) {
mc.checkers = append(mc.checkers, checker)
}

func (mc *ModChecker) CheckersSize() int {
if mc.checkers == nil {
return 0
}
return len(mc.checkers)
}

// Check runs all individual checks for a kclPkg.
func (dc *DepChecker) Check(kclPkg pkg.KclPkg) error {
for _, checker := range dc.checkers {
func (mc *ModChecker) Check(kclPkg pkg.KclPkg) error {
for _, checker := range mc.checkers {
if err := checker.Check(kclPkg); err != nil {
return err
}
Expand All @@ -75,11 +92,8 @@ func NewIdentChecker() *IdentChecker {
}

func (ic *IdentChecker) Check(kclPkg pkg.KclPkg) error {
for _, key := range kclPkg.Dependencies.Deps.Keys() {
dep, _ := kclPkg.Dependencies.Deps.Get(key)
if !isValidDependencyName(dep.Name) {
return fmt.Errorf("invalid dependency name: %s", dep.Name)
}
if !isValidDependencyName(kclPkg.ModFile.Pkg.Name) {
return fmt.Errorf("invalid name: %s", kclPkg.ModFile.Pkg.Name)
}
return nil
}
Expand All @@ -93,12 +107,11 @@ func NewVersionChecker() *VersionChecker {
}

func (vc *VersionChecker) Check(kclPkg pkg.KclPkg) error {
for _, key := range kclPkg.Dependencies.Deps.Keys() {
dep, _ := kclPkg.Dependencies.Deps.Get(key)
if !isValidDependencyVersion(dep.Version) {
return fmt.Errorf("invalid dependency version: %s for %s", dep.Version, dep.Name)
}
if !isValidDependencyVersion(kclPkg.ModFile.Pkg.Version) {
return fmt.Errorf("invalid version: %s for %s",
kclPkg.ModFile.Pkg.Version, kclPkg.ModFile.Pkg.Name)
}

return nil
}

Expand Down Expand Up @@ -147,7 +160,7 @@ func (sc *SumChecker) Check(kclPkg pkg.KclPkg) error {

// isValidDependencyName checks whether the given dependency name is valid.
func isValidDependencyName(name string) bool {
validNamePattern := `^[a-zA-Z][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_]$`
validNamePattern := `^[a-z][a-z0-9_]*(?:-[a-z0-9_]+)*$`
regex := regexp.MustCompile(validNamePattern)
return regex.MatchString(name)
}
Expand Down
90 changes: 31 additions & 59 deletions pkg/checker/checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
"kcl-lang.io/kpm/pkg/settings"
)

func TestDepCheckerCheck(t *testing.T) {
depChecker := NewDepChecker(WithCheckers(NewIdentChecker(), NewVersionChecker(), NewSumChecker()))
func TestModCheckerCheck(t *testing.T) {
ModChecker := NewModChecker(WithCheckers(NewIdentChecker(), NewVersionChecker(), NewSumChecker()))

deps1 := orderedmap.NewOrderedMap[string, pkg.Dependency]()
deps1.Set("kcl1", pkg.Dependency{
Expand All @@ -31,22 +31,6 @@ func TestDepCheckerCheck(t *testing.T) {
Sum: "no-sum-check-enabled",
})

deps2 := orderedmap.NewOrderedMap[string, pkg.Dependency]()
deps2.Set("kcl1", pkg.Dependency{
Name: ".kcl1",
FullName: "kcl1",
Version: "0.0.1",
Sum: "",
})

deps3 := orderedmap.NewOrderedMap[string, pkg.Dependency]()
deps3.Set("kcl1", pkg.Dependency{
Name: "kcl1",
FullName: "kcl1",
Version: "1.0.0-alpha#",
Sum: "",
})

tests := []struct {
name string
KclPkg pkg.KclPkg
Expand All @@ -56,6 +40,10 @@ func TestDepCheckerCheck(t *testing.T) {
name: "valid kcl package - with no sum check enabled",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
Pkg: pkg.Package{
Name: "testmod",
Version: "0.0.1",
},
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Expand All @@ -66,40 +54,12 @@ func TestDepCheckerCheck(t *testing.T) {
},
wantErr: false,
},
{
name: "Invalid kcl package - invalid dependency name",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Dependencies: pkg.Dependencies{
Deps: deps2,
},
NoSumCheck: false,
},
wantErr: true,
},
{
name: "Invalid kcl package - invalid dependency version",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Dependencies: pkg.Dependencies{
Deps: deps3,
},
NoSumCheck: false,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotErr := depChecker.Check(tt.KclPkg)
gotErr := ModChecker.Check(tt.KclPkg)
if (gotErr != nil) != tt.wantErr {
t.Errorf("depChecker.Check(%v) = %v, want error %v", tt.KclPkg, gotErr, tt.wantErr)
t.Errorf("ModChecker.Check(%v) = %v, want error %v", tt.KclPkg, gotErr, tt.wantErr)
}
})
}
Expand All @@ -112,12 +72,12 @@ func TestIsValidDependencyName(t *testing.T) {
want bool
}{
{"Empty Name", "", false},
{"Valid Name - Simple", "myDependency", true},
{"Valid Name - Simple", "myDependency", false},
{"Valid Name - With Underscore", "my_dependency", true},
{"Valid Name - With Hyphen", "my-dependency", true},
{"Valid Name - With Dot", "my.dependency", true},
{"Valid Name - Mixed Case", "MyDependency", true},
{"Valid Name - Long Name", "My_Very-Long.Dependency", true},
{"Valid Name - With Dot", "my.dependency", false},
{"Valid Name - Mixed Case", "MyDependency", false},
{"Valid Name - Long Name", "My_Very-Long.Dependency", false},
{"Contains Number", "depend3ncy", true},
{"Starts with Special Character", "-dependency", false},
{"Starts and Ends with Dot", ".dependency.", false},
Expand Down Expand Up @@ -184,9 +144,9 @@ func getTestSettings() (*settings.Settings, error) {
return settings, nil
}

func TestDepCheckerCheck_WithTrustedSum(t *testing.T) {
func TestModCheckerCheck_WithTrustedSum(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skipping TestDepCheckerCheck_WithTrustedSum test on Windows")
t.Skip("Skipping TestModCheckerCheck_WithTrustedSum test on Windows")
}

// Start the local Docker registry required for testing
Expand All @@ -197,12 +157,12 @@ func TestDepCheckerCheck_WithTrustedSum(t *testing.T) {
err = mock.PushTestPkgToRegistry()
assert.Equal(t, err, nil)

// Initialize settings for use with the DepChecker
// Initialize settings for use with the ModChecker
settings, err := getTestSettings()
assert.Equal(t, err, nil)

// Initialize the DepChecker with required checkers
depChecker := NewDepChecker(WithCheckers(NewIdentChecker(), NewVersionChecker(), NewSumChecker(WithSettings(*settings))))
// Initialize the ModChecker with required checkers
ModChecker := NewModChecker(WithCheckers(NewIdentChecker(), NewVersionChecker(), NewSumChecker(WithSettings(*settings))))

deps1 := orderedmap.NewOrderedMap[string, pkg.Dependency]()
deps1.Set("kcl1", pkg.Dependency{
Expand Down Expand Up @@ -243,6 +203,10 @@ func TestDepCheckerCheck_WithTrustedSum(t *testing.T) {
name: "valid kcl package - with sum check",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
Pkg: pkg.Package{
Name: "testmod",
Version: "0.0.1",
},
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Expand All @@ -257,6 +221,10 @@ func TestDepCheckerCheck_WithTrustedSum(t *testing.T) {
name: "valid kcl package - with no sum check enabled",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
Pkg: pkg.Package{
Name: "testmod",
Version: "0.0.1",
},
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Expand All @@ -271,6 +239,10 @@ func TestDepCheckerCheck_WithTrustedSum(t *testing.T) {
name: "Invalid kcl package - with no sum check disabled - checksum mismatches",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
Pkg: pkg.Package{
Name: "testmod",
Version: "0.0.1",
},
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Expand All @@ -285,9 +257,9 @@ func TestDepCheckerCheck_WithTrustedSum(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotErr := depChecker.Check(tt.KclPkg)
gotErr := ModChecker.Check(tt.KclPkg)
if (gotErr != nil) != tt.wantErr {
t.Errorf("depChecker.Check(%v) = %v, want error %v", tt.KclPkg, gotErr, tt.wantErr)
t.Errorf("ModChecker.Check(%v) = %v, want error %v", tt.KclPkg, gotErr, tt.wantErr)
}
})
}
Expand Down
57 changes: 57 additions & 0 deletions pkg/client/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package client

import (
"fmt"

"kcl-lang.io/kpm/pkg/checker"
pkg "kcl-lang.io/kpm/pkg/package"
)

type CheckOptions struct {
KclMod *pkg.KclPkg
}

type CheckOption func(*CheckOptions) error

func WithCheckKclMod(kclMod *pkg.KclPkg) CheckOption {
return func(opts *CheckOptions) error {
if kclMod == nil {
return fmt.Errorf("kclMod cannot be nil")
}
opts.KclMod = kclMod
return nil
}
}

func (c *KpmClient) Check(options ...CheckOption) error {
opts := &CheckOptions{}
for _, option := range options {
if err := option(opts); err != nil {
return err
}
}

kmod := opts.KclMod
if kmod == nil {
return fmt.Errorf("kclMod cannot be nil")
}

// Init the ModChecker, name and version checkers are required.
if c.ModChecker == nil || c.ModChecker.CheckersSize() == 0 {
c.ModChecker = checker.NewModChecker(
checker.WithCheckers(
checker.NewIdentChecker(),
checker.NewVersionChecker(),
checker.NewSumChecker(),
),
)
}

// Check the module and the dependencies
err := c.ModChecker.Check(*kmod)
if err != nil {
return err
}

return err
}
Loading

0 comments on commit 870a418

Please sign in to comment.