diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f7162ad0c..07bae84cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,5 +24,13 @@ jobs: check-latest: true - name: Build CLI run: make build-linux-amd64 - - name: Check version + - name: Run Unit Tests + run: make test + - name: Check Version run: bin/linux/amd64/oras version + - name: Upload Coverage Report + uses: actions/upload-artifact@master + with: + name: oras-coverage-report-${{ github.sha }} + path: .cover/ + if: always() diff --git a/Makefile b/Makefile index f0f0c1daf..eaa9faf68 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ LDFLAGS += -X $(PROJECT_PKG)/internal/version.GitTreeState=${GIT_DIRTY} .PHONY: test test: vendor check-encoding - echo "TODO: add unit tests" + ./scripts/test.sh .PHONY: covhtml covhtml: diff --git a/cmd/oras/internal/option/common_test.go b/cmd/oras/internal/option/common_test.go new file mode 100644 index 000000000..6a035a820 --- /dev/null +++ b/cmd/oras/internal/option/common_test.go @@ -0,0 +1,30 @@ +/* +Copyright The ORAS Authors. +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 option + +import ( + "testing" + + "github.com/spf13/pflag" +) + +func TestCommon_FlagsInit(t *testing.T) { + var test struct { + Common + } + + ApplyFlags(&test, pflag.NewFlagSet("oras-test", pflag.ExitOnError)) +} diff --git a/cmd/oras/internal/option/remote_test.go b/cmd/oras/internal/option/remote_test.go index c3c9b786a..ee6799e19 100644 --- a/cmd/oras/internal/option/remote_test.go +++ b/cmd/oras/internal/option/remote_test.go @@ -20,18 +20,29 @@ import ( "crypto/rand" "crypto/x509" "encoding/base64" + "encoding/json" "encoding/pem" + "fmt" "os" "path/filepath" + "reflect" "testing" nhttp "net/http" "net/http/httptest" + "net/url" + "github.com/spf13/pflag" "oras.land/oras-go/v2/registry/remote/auth" ) var ts *httptest.Server +var testRepo = "test-repo" +var testTagList = struct { + Tags []string `json:"tags"` +}{ + Tags: []string{"tag"}, +} func TestMain(m *testing.M) { // Test server @@ -41,13 +52,23 @@ func TestMain(m *testing.M) { switch { case p == "/v2/" && m == "GET": w.WriteHeader(nhttp.StatusOK) + case p == fmt.Sprintf("/v2/%s/tags/list", testRepo) && m == "GET": + json.NewEncoder(w).Encode(testTagList) } })) defer ts.Close() m.Run() } -func Test_authClient_RawCredential(t *testing.T) { +func TestRemote_FlagsInit(t *testing.T) { + var test struct { + Remote + } + + ApplyFlags(&test, pflag.NewFlagSet("oras-test", pflag.ExitOnError)) +} + +func TestRemote_authClient_RawCredential(t *testing.T) { password := make([]byte, 12) if _, err := rand.Read(password); err != nil { t.Fatalf("unexpected error: %v", err) @@ -74,7 +95,7 @@ func Test_authClient_RawCredential(t *testing.T) { } } -func Test_authClient_skipTlsVerify(t *testing.T) { +func TestRemote_authClient_skipTlsVerify(t *testing.T) { opts := Remote{ Insecure: true, } @@ -92,7 +113,7 @@ func Test_authClient_skipTlsVerify(t *testing.T) { } } -func Test_authClient_CARoots(t *testing.T) { +func TestRemote_authClient_CARoots(t *testing.T) { caPath := filepath.Join(t.TempDir(), "oras-test.pem") if err := os.WriteFile(caPath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ts.Certificate().Raw}), 0644); err != nil { t.Fatalf("unexpected error: %v", err) @@ -117,3 +138,70 @@ func Test_authClient_CARoots(t *testing.T) { t.Fatalf("unexpected error: %v", err) } } + +func TestRemote_NewRegistry(t *testing.T) { + caPath := filepath.Join(t.TempDir(), "oras-test.pem") + if err := os.WriteFile(caPath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ts.Certificate().Raw}), 0644); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + pool := x509.NewCertPool() + pool.AddCert(ts.Certificate()) + + opts := struct { + Remote + Common + }{ + Remote{ + CACertFilePath: caPath, + }, + Common{}, + } + uri, err := url.ParseRequestURI(ts.URL) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + reg, err := opts.NewRegistry(uri.Host, opts.Common) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if err = reg.Ping(context.Background()); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestRemote_NewRepository(t *testing.T) { + caPath := filepath.Join(t.TempDir(), "oras-test.pem") + if err := os.WriteFile(caPath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ts.Certificate().Raw}), 0644); err != nil { + t.Fatalf("unexpected error: %v", err) + } + pool := x509.NewCertPool() + pool.AddCert(ts.Certificate()) + opts := struct { + Remote + Common + }{ + Remote{ + CACertFilePath: caPath, + }, + Common{}, + } + + uri, err := url.ParseRequestURI(ts.URL) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + repo, err := opts.NewRepository(uri.Host+"/"+testRepo, opts.Common) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if err = repo.Tags(context.Background(), func(got []string) error { + want := []string{"tag"} + if len(got) != len(testTagList.Tags) || !reflect.DeepEqual(got, want) { + return fmt.Errorf("expect: %v, got: %v", testTagList.Tags, got) + } + return nil + }); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 000000000..432f51d89 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,33 @@ +#!/bin/bash -ex + +# Copyright The ORAS Authors. +# +# 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. + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd $DIR/../ + +rm -rf .cover/ .test/ +mkdir .cover/ .test/ +trap "rm -rf .test/" EXIT + +export CGO_ENABLED=0 +for pkg in `go list ./...`; do + go test -v -covermode=atomic \ + -coverprofile=".cover/$(echo $pkg | sed 's/\//_/g').cover.out" $pkg +done + +echo "mode: set" > .cover/cover.out && cat .cover/*.cover.out | grep -v mode: | sort -r | \ + awk '{if($1 != last) {print $0;last=$1}}' >> .cover/cover.out + +go tool cover -html=.cover/cover.out -o=.cover/coverage.html \ No newline at end of file