Repository: apigee/registry
Pull request #978 (2023-01-26 20:35:48)

Push subcommands of `registry compute` into separate directories.
Addresses #944 by pushing `registry compute` subcommand implementations into separate directories that can be independently prioritized for test coverage. Three subcommands were untested, so to keep them in our coverage stats, I added stub tests in each three that just verify that a command that is run without arguments returns an error.

The division causes some code to be duplicated that was previously shared in the `compute` package. I don't think it's significant, and would like to leave it to be addressed in any future refactoring of the tests that aims to reduce duplication (I don't think this is currently a priority). Also, flag declarations are pushed down to the leaf level because they are needed in leaf-level tests (and including the `compute` package creates an import cycle).

`testdata` remains shared across the `compute` package - I think reorganization of that would be best left to PRs focused on testing individual commands.

codecov[bot]: # [Codecov](https://codecov.io/gh/apigee/registry/pull/978?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) Report
> Merging [#978](https://codecov.io/gh/apigee/registry/pull/978?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) (dfe95b3) into [main](https://codecov.io/gh/apigee/registry/commit/0cbf20a6fa202ebfb86f6a8498fbf571c0fc6048?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) (0cbf20a) will **increase** coverage by `0.01%`.
> The diff coverage is `100.00%`.

> :exclamation: Current head dfe95b3 differs from pull request most recent head 03011a6. Consider uploading reports for the commit 03011a6 to get more accurate results

```diff
@@            Coverage Diff             @@
##             main     #978      +/-   ##
==========================================
+ Coverage   67.59%   67.61%   +0.01%     
==========================================
  Files         128      128              
  Lines       10947    10951       +4     
==========================================
+ Hits         7400     7404       +4     
  Misses       2917     2917              
  Partials      630      630              
```


| [Impacted Files](https://codecov.io/gh/apigee/registry/pull/978?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) | Coverage Δ | |
|---|---|---|
| [cmd/registry/cmd/compute/compute.go](https://codecov.io/gh/apigee/registry/pull/978?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L2NtZC9jb21wdXRlL2NvbXB1dGUuZ28=) | `100.00% <100.00%> (ø)` | |
| [...md/registry/cmd/compute/conformance/conformance.go](https://codecov.io/gh/apigee/registry/pull/978?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#diff-Y21kL3JlZ2lzdHJ5L2NtZC9jb21wdXRlL2NvbmZvcm1hbmNlL2NvbmZvcm1hbmNlLmdv) | `55.68% <100.00%> (ø)` | |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee)


Patch:
diff --git a/cmd/registry/cmd/compute/complexity-discovery.go b/cmd/registry/cmd/compute/complexity/complexity-discovery.go
similarity index 99%
rename from cmd/registry/cmd/compute/complexity-discovery.go
rename to cmd/registry/cmd/compute/complexity/complexity-discovery.go
index eb0de37..2545de3 100644
--- a/cmd/registry/cmd/compute/complexity-discovery.go
+++ b/cmd/registry/cmd/compute/complexity/complexity-discovery.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package complexity
 
 import (
 	discovery "github.com/google/gnostic/discovery"
diff --git a/cmd/registry/cmd/compute/complexity-openapi.go b/cmd/registry/cmd/compute/complexity/complexity-openapi.go
similarity index 99%
rename from cmd/registry/cmd/compute/complexity-openapi.go
rename to cmd/registry/cmd/compute/complexity/complexity-openapi.go
index c245bce..2263f27 100644
--- a/cmd/registry/cmd/compute/complexity-openapi.go
+++ b/cmd/registry/cmd/compute/complexity/complexity-openapi.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package complexity
 
 import (
 	metrics "github.com/google/gnostic/metrics"
diff --git a/cmd/registry/cmd/compute/complexity-proto.go b/cmd/registry/cmd/compute/complexity/complexity-proto.go
similarity index 99%
rename from cmd/registry/cmd/compute/complexity-proto.go
rename to cmd/registry/cmd/compute/complexity/complexity-proto.go
index 8cee386..b885494 100644
--- a/cmd/registry/cmd/compute/complexity-proto.go
+++ b/cmd/registry/cmd/compute/complexity/complexity-proto.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package complexity
 
 import (
 	"archive/zip"
diff --git a/cmd/registry/cmd/compute/complexity.go b/cmd/registry/cmd/compute/complexity/complexity.go
similarity index 93%
rename from cmd/registry/cmd/compute/complexity.go
rename to cmd/registry/cmd/compute/complexity/complexity.go
index c92486f..7506f02 100644
--- a/cmd/registry/cmd/compute/complexity.go
+++ b/cmd/registry/cmd/compute/complexity/complexity.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package complexity
 
 import (
 	"context"
@@ -34,8 +34,8 @@ import (
 	oas3 "github.com/google/gnostic/openapiv3"
 )
 
-func complexityCommand() *cobra.Command {
-	return &cobra.Command{
+func Command() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:   "complexity",
 		Short: "Compute complexity metrics of API specs",
 		Args:  cobra.ExactArgs(1),
@@ -98,6 +98,10 @@ func complexityCommand() *cobra.Command {
 			}
 		},
 	}
+	cmd.Flags().String("filter", "", "Filter selected resources")
+	cmd.Flags().Bool("dry-run", false, "if set, computation results will only be printed and will not stored in the registry")
+	cmd.Flags().Int("jobs", 10, "Number of actions to perform concurrently")
+	return cmd
 }
 
 type computeComplexityTask struct {
diff --git a/cmd/registry/cmd/compute/compute.go b/cmd/registry/cmd/compute/compute.go
index 30007f4..bf851cb 100644
--- a/cmd/registry/cmd/compute/compute.go
+++ b/cmd/registry/cmd/compute/compute.go
@@ -15,6 +15,13 @@
 package compute
 
 import (
+	"github.com/apigee/registry/cmd/registry/cmd/compute/complexity"
+	"github.com/apigee/registry/cmd/registry/cmd/compute/conformance"
+	"github.com/apigee/registry/cmd/registry/cmd/compute/lint"
+	"github.com/apigee/registry/cmd/registry/cmd/compute/lintstats"
+	"github.com/apigee/registry/cmd/registry/cmd/compute/score"
+	"github.com/apigee/registry/cmd/registry/cmd/compute/scorecard"
+	"github.com/apigee/registry/cmd/registry/cmd/vocabulary"
 	"github.com/spf13/cobra"
 )
 
@@ -24,13 +31,13 @@ func Command() *cobra.Command {
 		Short: "Compute properties of resources in the API Registry",
 	}
 
-	cmd.AddCommand(conformanceCommand())
-	cmd.AddCommand(complexityCommand())
-	cmd.AddCommand(lintCommand())
-	cmd.AddCommand(lintStatsCommand())
-	cmd.AddCommand(scoreCommand())
-	cmd.AddCommand(scoreCardCommand())
-	cmd.AddCommand(vocabularyCommand())
+	cmd.AddCommand(conformance.Command())
+	cmd.AddCommand(complexity.Command())
+	cmd.AddCommand(lint.Command())
+	cmd.AddCommand(lintstats.Command())
+	cmd.AddCommand(score.Command())
+	cmd.AddCommand(scorecard.Command())
+	cmd.AddCommand(vocabulary.Command())
 
 	cmd.PersistentFlags().String("filter", "", "Filter selected resources")
 	cmd.PersistentFlags().Bool("dry-run", false, "if set, computation results will only be printed and will not stored in the registry")
diff --git a/cmd/registry/cmd/compute/conformance.go b/cmd/registry/cmd/compute/conformance/conformance.go
similarity index 93%
rename from cmd/registry/cmd/compute/conformance.go
rename to cmd/registry/cmd/compute/conformance/conformance.go
index c68700c..14d5eba 100644
--- a/cmd/registry/cmd/compute/conformance.go
+++ b/cmd/registry/cmd/compute/conformance/conformance.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package conformance
 
 import (
 	"context"
@@ -31,7 +31,7 @@ import (
 
 var styleguideFilter = fmt.Sprintf("mime_type.contains('%s')", types.MimeTypeForKind("StyleGuide"))
 
-func conformanceCommand() *cobra.Command {
+func Command() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "conformance",
 		Short: "Compute lint results for API specs",
@@ -102,7 +102,9 @@ func conformanceCommand() *cobra.Command {
 			}
 		},
 	}
-
+	cmd.Flags().String("filter", "", "Filter selected resources")
+	cmd.Flags().Bool("dry-run", false, "if set, computation results will only be printed and will not stored in the registry")
+	cmd.Flags().Int("jobs", 10, "Number of actions to perform concurrently")
 	return cmd
 }
 
diff --git a/cmd/registry/cmd/compute/lint-openapi-gnostic.go b/cmd/registry/cmd/compute/lint/lint-openapi-gnostic.go
similarity index 99%
rename from cmd/registry/cmd/compute/lint-openapi-gnostic.go
rename to cmd/registry/cmd/compute/lint/lint-openapi-gnostic.go
index 3fb5a68..2f65d81 100644
--- a/cmd/registry/cmd/compute/lint-openapi-gnostic.go
+++ b/cmd/registry/cmd/compute/lint/lint-openapi-gnostic.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package lint
 
 import (
 	"os"
diff --git a/cmd/registry/cmd/compute/lint-openapi-spectral.go b/cmd/registry/cmd/compute/lint/lint-openapi-spectral.go
similarity index 99%
rename from cmd/registry/cmd/compute/lint-openapi-spectral.go
rename to cmd/registry/cmd/compute/lint/lint-openapi-spectral.go
index 6c95626..450fc56 100644
--- a/cmd/registry/cmd/compute/lint-openapi-spectral.go
+++ b/cmd/registry/cmd/compute/lint/lint-openapi-spectral.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package lint
 
 import (
 	"encoding/json"
diff --git a/cmd/registry/cmd/compute/lint-openapi.go b/cmd/registry/cmd/compute/lint/lint-openapi.go
similarity index 99%
rename from cmd/registry/cmd/compute/lint-openapi.go
rename to cmd/registry/cmd/compute/lint/lint-openapi.go
index 676eb86..e255690 100644
--- a/cmd/registry/cmd/compute/lint-openapi.go
+++ b/cmd/registry/cmd/compute/lint/lint-openapi.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package lint
 
 import (
 	"errors"
diff --git a/cmd/registry/cmd/compute/lint-proto.go b/cmd/registry/cmd/compute/lint/lint-proto.go
similarity index 99%
rename from cmd/registry/cmd/compute/lint-proto.go
rename to cmd/registry/cmd/compute/lint/lint-proto.go
index 1e6ff6a..a010fa6 100644
--- a/cmd/registry/cmd/compute/lint-proto.go
+++ b/cmd/registry/cmd/compute/lint/lint-proto.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package lint
 
 import (
 	"os"
diff --git a/cmd/registry/cmd/compute/lint.go b/cmd/registry/cmd/compute/lint/lint.go
similarity index 98%
rename from cmd/registry/cmd/compute/lint.go
rename to cmd/registry/cmd/compute/lint/lint.go
index 0b07eaa..19496da 100644
--- a/cmd/registry/cmd/compute/lint.go
+++ b/cmd/registry/cmd/compute/lint/lint.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package lint
 
 import (
 	"context"
@@ -28,7 +28,7 @@ import (
 	"google.golang.org/protobuf/proto"
 )
 
-func lintCommand() *cobra.Command {
+func Command() *cobra.Command {
 	var linter string
 	cmd := &cobra.Command{
 		Use:   "lint",
diff --git a/cmd/registry/cmd/compute/lintstats.go b/cmd/registry/cmd/compute/lintstats/lintstats.go
similarity index 98%
rename from cmd/registry/cmd/compute/lintstats.go
rename to cmd/registry/cmd/compute/lintstats/lintstats.go
index 248315c..a5251aa 100644
--- a/cmd/registry/cmd/compute/lintstats.go
+++ b/cmd/registry/cmd/compute/lintstats/lintstats.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package lintstats
 
 import (
 	"context"
@@ -32,11 +32,15 @@ import (
 	metrics "github.com/google/gnostic/metrics"
 )
 
+func lintRelation(linter string) string {
+	return "lint-" + linter
+}
+
 func lintStatsRelation(linter string) string {
 	return "lintstats-" + linter
 }
 
-func lintStatsCommand() *cobra.Command {
+func Command() *cobra.Command {
 	var linter string
 	cmd := &cobra.Command{
 		Use:   "lintstats",
diff --git a/cmd/registry/cmd/compute/score.go b/cmd/registry/cmd/compute/score/score.go
similarity index 91%
rename from cmd/registry/cmd/compute/score.go
rename to cmd/registry/cmd/compute/score/score.go
index 6c72ad7..5f35fc4 100644
--- a/cmd/registry/cmd/compute/score.go
+++ b/cmd/registry/cmd/compute/score/score.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package score
 
 import (
 	"context"
@@ -27,8 +27,8 @@ import (
 	"google.golang.org/protobuf/proto"
 )
 
-func scoreCommand() *cobra.Command {
-	return &cobra.Command{
+func Command() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:   "score",
 		Short: "Compute scores for APIs and API specs",
 		Args:  cobra.ExactArgs(1),
@@ -99,6 +99,10 @@ func scoreCommand() *cobra.Command {
 			}
 		},
 	}
+	cmd.Flags().String("filter", "", "Filter selected resources")
+	cmd.Flags().Bool("dry-run", false, "if set, computation results will only be printed and will not stored in the registry")
+	cmd.Flags().Int("jobs", 10, "Number of actions to perform concurrently")
+	return cmd
 }
 
 type computeScoreTask struct {
diff --git a/cmd/registry/cmd/compute/scorecard.go b/cmd/registry/cmd/compute/scorecard/scorecard.go
similarity index 91%
rename from cmd/registry/cmd/compute/scorecard.go
rename to cmd/registry/cmd/compute/scorecard/scorecard.go
index ba0146f..b3358be 100644
--- a/cmd/registry/cmd/compute/scorecard.go
+++ b/cmd/registry/cmd/compute/scorecard/scorecard.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package scorecard
 
 import (
 	"context"
@@ -27,8 +27,8 @@ import (
 	"google.golang.org/protobuf/proto"
 )
 
-func scoreCardCommand() *cobra.Command {
-	return &cobra.Command{
+func Command() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:   "scorecard",
 		Short: "Compute score cards for APIs and API specs",
 		Args:  cobra.ExactArgs(1),
@@ -98,6 +98,10 @@ func scoreCardCommand() *cobra.Command {
 			}
 		},
 	}
+	cmd.Flags().String("filter", "", "Filter selected resources")
+	cmd.Flags().Bool("dry-run", false, "if set, computation results will only be printed and will not stored in the registry")
+	cmd.Flags().Int("jobs", 10, "Number of actions to perform concurrently")
+	return cmd
 }
 
 type computeScoreCardTask struct {
diff --git a/cmd/registry/cmd/compute/vocabulary-proto.go b/cmd/registry/cmd/compute/vocabulary/vocabulary-proto.go
similarity index 99%
rename from cmd/registry/cmd/compute/vocabulary-proto.go
rename to cmd/registry/cmd/compute/vocabulary/vocabulary-proto.go
index c10e001..4904f3e 100644
--- a/cmd/registry/cmd/compute/vocabulary-proto.go
+++ b/cmd/registry/cmd/compute/vocabulary/vocabulary-proto.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package vocabulary
 
 import (
 	"archive/zip"
diff --git a/cmd/registry/cmd/compute/vocabulary.go b/cmd/registry/cmd/compute/vocabulary/vocabulary.go
similarity index 98%
rename from cmd/registry/cmd/compute/vocabulary.go
rename to cmd/registry/cmd/compute/vocabulary/vocabulary.go
index 22e42fa..d09d7de 100644
--- a/cmd/registry/cmd/compute/vocabulary.go
+++ b/cmd/registry/cmd/compute/vocabulary/vocabulary.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package vocabulary
 
 import (
 	"context"
@@ -34,7 +34,7 @@ import (
 	oas3 "github.com/google/gnostic/openapiv3"
 )
 
-func vocabularyCommand() *cobra.Command {
+func Command() *cobra.Command {
 	return &cobra.Command{
 		Use:   "vocabulary",
 		Short: "Compute vocabularies of API specs",


Test patch:
diff --git a/cmd/registry/cmd/compute/complexity_test.go b/cmd/registry/cmd/compute/complexity/complexity_test.go
similarity index 85%
rename from cmd/registry/cmd/compute/complexity_test.go
rename to cmd/registry/cmd/compute/complexity/complexity_test.go
index 4d53318..338ec7e 100644
--- a/cmd/registry/cmd/compute/complexity_test.go
+++ b/cmd/registry/cmd/compute/complexity/complexity_test.go
@@ -12,15 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package complexity
 
 import (
+	"bytes"
+	"compress/gzip"
 	"context"
+	"os"
 	"path/filepath"
 	"testing"
 
 	"github.com/apigee/registry/pkg/connection"
+	"github.com/apigee/registry/pkg/connection/grpctest"
 	"github.com/apigee/registry/rpc"
+	"github.com/apigee/registry/server/registry"
 	"github.com/apigee/registry/server/registry/names"
 	metrics "github.com/google/gnostic/metrics"
 	"github.com/google/go-cmp/cmp"
@@ -30,6 +35,27 @@ import (
 	"google.golang.org/protobuf/testing/protocmp"
 )
 
+// TestMain will set up a local RegistryServer and grpc.Server for all
+// tests in this package if APG_REGISTRY_ADDRESS env var is not set
+// for the client.
+func TestMain(m *testing.M) {
+	grpctest.TestMain(m, registry.Config{})
+}
+
+func readAndGZipFile(t *testing.T, filename string) (*bytes.Buffer, error) {
+	t.Helper()
+	fileBytes, _ := os.ReadFile(filename)
+	var buf bytes.Buffer
+	zw, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
+	_, err := zw.Write(fileBytes)
+	if err != nil {
+		return nil, err
+	}
+	if err := zw.Close(); err != nil {
+		return nil, err
+	}
+	return &buf, nil
+}
 func TestComplexity(t *testing.T) {
 	tests := []struct {
 		desc       string
@@ -107,7 +133,7 @@ func TestComplexity(t *testing.T) {
 			if err != nil {
 				t.Fatalf("Failed to create version %s: %s", test.versionId, err.Error())
 			}
-			buf, err := readAndGZipFile(t, filepath.Join("testdata", test.specFile))
+			buf, err := readAndGZipFile(t, filepath.Join("..", "testdata", test.specFile))
 			if err != nil {
 				t.Fatalf("Failed reading spec contents: %s", err.Error())
 			}
@@ -124,7 +150,7 @@ func TestComplexity(t *testing.T) {
 				t.Fatalf("Failed CreateApiSpec(%v): %s", req, err.Error())
 			}
 			complexityCommand := Command()
-			args := []string{"complexity", spec.Name}
+			args := []string{spec.Name}
 			complexityCommand.SetArgs(args)
 			if err = complexityCommand.Execute(); err != nil {
 				t.Fatalf("Execute() with args %v returned error: %s", args, err)
diff --git a/cmd/registry/cmd/compute/compute_test.go b/cmd/registry/cmd/compute/compute_test.go
index af73738..656f8c1 100644
--- a/cmd/registry/cmd/compute/compute_test.go
+++ b/cmd/registry/cmd/compute/compute_test.go
@@ -16,14 +16,10 @@ package compute
 
 import (
 	"testing"
-
-	"github.com/apigee/registry/pkg/connection/grpctest"
-	"github.com/apigee/registry/server/registry"
 )
 
-// TestMain will set up a local RegistryServer and grpc.Server for all
-// tests in this package if APG_REGISTRY_ADDRESS env var is not set
-// for the client.
-func TestMain(m *testing.M) {
-	grpctest.TestMain(m, registry.Config{})
+func TestCommand(t *testing.T) {
+	if Command() == nil {
+		t.Fatalf("Command() failed to return a value")
+	}
 }
diff --git a/cmd/registry/cmd/compute/conformance_test.go b/cmd/registry/cmd/compute/conformance/conformance_test.go
similarity index 95%
rename from cmd/registry/cmd/compute/conformance_test.go
rename to cmd/registry/cmd/compute/conformance/conformance_test.go
index 63c5225..c043f03 100644
--- a/cmd/registry/cmd/compute/conformance_test.go
+++ b/cmd/registry/cmd/compute/conformance/conformance_test.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package compute
+package conformance
 
 import (
 	"bytes"
@@ -25,7 +25,9 @@ import (
 
 	"github.com/apigee/registry/cmd/registry/cmd/apply"
 	"github.com/apigee/registry/pkg/connection"
+	"github.com/apigee/registry/pkg/connection/grpctest"
 	"github.com/apigee/registry/rpc"
+	"github.com/apigee/registry/server/registry"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"google.golang.org/grpc/codes"
@@ -34,6 +36,13 @@ import (
 	"google.golang.org/protobuf/testing/protocmp"
 )
 
+// TestMain will set up a local RegistryServer and grpc.Server for all
+// tests in this package if APG_REGISTRY_ADDRESS env var is not set
+// for the client.
+func TestMain(m *testing.M) {
+	grpctest.TestMain(m, registry.Config{})
+}
+
 func readAndGZipFile(t *testing.T, filename string) (*bytes.Buffer, error) {
 	t.Helper()
 	fileBytes, _ := os.ReadFile(filename)
@@ -59,7 +68,7 @@ func TestConformance(t *testing.T) {
 		//Tests the normal use case with one guideline defined with state: ACTIVE and one Rule defined with severity:ERROR
 		{
 			desc:            "normal case",
-			conformancePath: filepath.Join("testdata", "styleguide.yaml"),
+			conformancePath: filepath.Join("..", "testdata", "styleguide.yaml"),
 			getPattern:      "projects/conformance-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/conformance-openapitest",
 			wantProto: &rpc.ConformanceReport{
 				Id:         "conformance-openapitest",
@@ -102,7 +111,7 @@ func TestConformance(t *testing.T) {
 		//Tests if default state and severity values are assigned properly in the absence of defined values
 		{
 			desc:            "default case",
-			conformancePath: filepath.Join("testdata", "styleguide-default.yaml"),
+			conformancePath: filepath.Join("..", "testdata", "styleguide-default.yaml"),
 			getPattern:      "projects/conformance-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/conformance-openapitest-default",
 			wantProto: &rpc.ConformanceReport{
 				Id:         "conformance-openapitest-default",
@@ -144,7 +153,7 @@ func TestConformance(t *testing.T) {
 		//Tests if multiple severity levels are populated correctly in severity report
 		{
 			desc:            "multiple severity",
-			conformancePath: filepath.Join("testdata", "styleguide-multiple-severity.yaml"),
+			conformancePath: filepath.Join("..", "testdata", "styleguide-multiple-severity.yaml"),
 			getPattern:      "projects/conformance-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/conformance-openapitest-multiple-severity",
 			wantProto: &rpc.ConformanceReport{
 				Id:         "conformance-openapitest-multiple-severity",
@@ -208,7 +217,7 @@ func TestConformance(t *testing.T) {
 		//Tests if multiple state entries are populated correctly in severity report
 		{
 			desc:            "multiple state",
-			conformancePath: filepath.Join("testdata", "styleguide-multiple-state.yaml"),
+			conformancePath: filepath.Join("..", "testdata", "styleguide-multiple-state.yaml"),
 			getPattern:      "projects/conformance-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/conformance-openapitest-multiple-state",
 			wantProto: &rpc.ConformanceReport{
 				Id:         "conformance-openapitest-multiple-state",
@@ -284,7 +293,7 @@ func TestConformance(t *testing.T) {
 		//Tests a guideline which defines rules from multiple linters
 		{
 			desc:            "multiple linter",
-			conformancePath: filepath.Join("testdata", "styleguide-multiple-linter.yaml"),
+			conformancePath: filepath.Join("..", "testdata", "styleguide-multiple-linter.yaml"),
 			getPattern:      "projects/conformance-test/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/conformance-openapitest-multiple-linter",
 			wantProto: &rpc.ConformanceReport{
 				Id:         "conformance-openapitest-multiple-linter",
@@ -435,7 +444,7 @@ func TestConformance(t *testing.T) {
 			}
 
 			// Create Spec in each of the versions
-			buf, err := readAndGZipFile(t, filepath.Join("testdata", "openapi.yaml"))
+			buf, err := readAndGZipFile(t, filepath.Join("..", "testdata", "openapi.yaml"))
 			if err != nil {
 				t.Fatalf("Failed reading API contents: %s", err.Error())
 			}
@@ -464,7 +473,7 @@ func TestConformance(t *testing.T) {
 
 			// setup the command
 			conformanceCmd := Command()
-			args = []string{"conformance", spec.Name}
+			args = []string{spec.Name}
 			conformanceCmd.SetArgs(args)
 
 			if err = conformanceCmd.Execute(); err != nil {
diff --git a/cmd/registry/cmd/compute/lint/lint_test.go b/cmd/registry/cmd/compute/lint/lint_test.go
new file mode 100644
index 0000000..1396147
--- /dev/null
+++ b/cmd/registry/cmd/compute/lint/lint_test.go
@@ -0,0 +1,36 @@
+// Copyright 2023 Google LLC
+//
+// 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 lint
+
+import (
+	"testing"
+
+	"github.com/apigee/registry/pkg/connection/grpctest"
+	"github.com/apigee/registry/server/registry"
+)
+
+// TestMain will set up a local RegistryServer and grpc.Server for all
+// tests in this package if APG_REGISTRY_ADDRESS env var is not set
+// for the client.
+func TestMain(m *testing.M) {
+	grpctest.TestMain(m, registry.Config{})
+}
+
+func TestLint(t *testing.T) {
+	command := Command()
+	if err := command.Execute(); err == nil {
+		t.Fatalf("Execute() with no args succeeded and should have failed")
+	}
+}
diff --git a/cmd/registry/cmd/compute/lintstats/lintstats_test.go b/cmd/registry/cmd/compute/lintstats/lintstats_test.go
new file mode 100644
index 0000000..c728354
--- /dev/null
+++ b/cmd/registry/cmd/compute/lintstats/lintstats_test.go
@@ -0,0 +1,36 @@
+// Copyright 2023 Google LLC
+//
+// 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 lintstats
+
+import (
+	"testing"
+
+	"github.com/apigee/registry/pkg/connection/grpctest"
+	"github.com/apigee/registry/server/registry"
+)
+
+// TestMain will set up a local RegistryServer and grpc.Server for all
+// tests in this package if APG_REGISTRY_ADDRESS env var is not set
+// for the client.
+func TestMain(m *testing.M) {
+	grpctest.TestMain(m, registry.Config{})
+}
+
+func TestLintStats(t *testing.T) {
+	command := Command()
+	if err := command.Execute(); err == nil {
+		t.Fatalf("Execute() with no args succeeded and should have failed")
+	}
+}
diff --git a/cmd/registry/cmd/compute/score_test.go b/cmd/registry/cmd/compute/score/score_test.go
similarity index 91%
rename from cmd/registry/cmd/compute/score_test.go
rename to cmd/registry/cmd/compute/score/score_test.go
index 520cd35..0d19b95 100644
--- a/cmd/registry/cmd/compute/score_test.go
+++ b/cmd/registry/cmd/compute/score/score_test.go
@@ -1,4 +1,17 @@
-package compute
+// Copyright 2022 Google LLC
+//
+// 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 score
 
 import (
 	"context"
@@ -6,7 +19,9 @@ import (
 	"testing"
 
 	"github.com/apigee/registry/pkg/connection"
+	"github.com/apigee/registry/pkg/connection/grpctest"
 	"github.com/apigee/registry/rpc"
+	"github.com/apigee/registry/server/registry"
 	"github.com/apigee/registry/server/registry/test/seeder"
 	"github.com/google/go-cmp/cmp"
 	"google.golang.org/api/iterator"
@@ -15,6 +30,13 @@ import (
 	"google.golang.org/protobuf/proto"
 )
 
+// TestMain will set up a local RegistryServer and grpc.Server for all
+// tests in this package if APG_REGISTRY_ADDRESS env var is not set
+// for the client.
+func TestMain(m *testing.M) {
+	grpctest.TestMain(m, registry.Config{})
+}
+
 const gzipOpenAPIv3 = "application/x.openapi+gzip;version=3.0.0"
 const gzipProtobuf = "application/x.protobuf+gzip"
 const scoreDefinitionType = "application/octet-stream;type=google.cloud.apigeeregistry.v1.scoring.ScoreDefinition"
@@ -303,7 +325,7 @@ func TestScore(t *testing.T) {
 
 			// setup the score command
 			scoreCmd := Command()
-			args := []string{"score", "projects/score-test/locations/global/apis/-/versions/-/specs/-"}
+			args := []string{"projects/score-test/locations/global/apis/-/versions/-/specs/-"}
 			scoreCmd.SetArgs(args)
 
 			if err = scoreCmd.Execute(); err != nil {
diff --git a/cmd/registry/cmd/compute/scorecard_test.go b/cmd/registry/cmd/compute/scorecard/scorecard_test.go
similarity index 84%
rename from cmd/registry/cmd/compute/scorecard_test.go
rename to cmd/registry/cmd/compute/scorecard/scorecard_test.go
index 3f87f8c..2f56d78 100644
--- a/cmd/registry/cmd/compute/scorecard_test.go
+++ b/cmd/registry/cmd/compute/scorecard/scorecard_test.go
@@ -1,4 +1,17 @@
-package compute
+// Copyright 2022 Google LLC
+//
+// 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 scorecard
 
 import (
 	"context"
@@ -6,15 +19,50 @@ import (
 	"testing"
 
 	"github.com/apigee/registry/pkg/connection"
+	"github.com/apigee/registry/pkg/connection/grpctest"
 	"github.com/apigee/registry/rpc"
+	"github.com/apigee/registry/server/registry"
 	"github.com/apigee/registry/server/registry/test/seeder"
 	"github.com/google/go-cmp/cmp"
 	"google.golang.org/api/iterator"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+	"google.golang.org/protobuf/proto"
 )
 
+// TestMain will set up a local RegistryServer and grpc.Server for all
+// tests in this package if APG_REGISTRY_ADDRESS env var is not set
+// for the client.
+func TestMain(m *testing.M) {
+	grpctest.TestMain(m, registry.Config{})
+}
+
+const gzipOpenAPIv3 = "application/x.openapi+gzip;version=3.0.0"
+const gzipProtobuf = "application/x.protobuf+gzip"
 const scoreCardDefinitionType = "application/octet-stream;type=google.cloud.apigeeregistry.v1.scoring.ScoreCardDefinition"
 const scoreType = "application/octet-stream;type=google.cloud.apigeeregistry.v1.scoring.Score"
 
+func protoMarshal(m proto.Message) []byte {
+	b, _ := proto.Marshal(m)
+	return b
+}
+
+func deleteProject(
+	ctx context.Context,
+	client connection.AdminClient,
+	t *testing.T,
+	projectID string) {
+	t.Helper()
+	req := &rpc.DeleteProjectRequest{
+		Name:  "projects/" + projectID,
+		Force: true,
+	}
+	err := client.DeleteProject(ctx, req)
+	if err != nil && status.Code(err) != codes.NotFound {
+		t.Fatalf("Failed DeleteProject(%v): %s", req, err.Error())
+	}
+}
+
 var (
 	scoreCardAll = &rpc.ScoreCardDefinition{
 		Id:   "lint-summary",
@@ -233,7 +281,7 @@ func TestScoreCard(t *testing.T) {
 
 			// setup the score command
 			scoreCardCmd := Command()
-			args := []string{"scorecard", "projects/scorecard-test/locations/global/apis/-/versions/-/specs/-"}
+			args := []string{"projects/scorecard-test/locations/global/apis/-/versions/-/specs/-"}
 			scoreCardCmd.SetArgs(args)
 
 			if err = scoreCardCmd.Execute(); err != nil {
diff --git a/cmd/registry/cmd/compute/vocabulary/vocabulary_test.go b/cmd/registry/cmd/compute/vocabulary/vocabulary_test.go
new file mode 100644
index 0000000..596f09b
--- /dev/null
+++ b/cmd/registry/cmd/compute/vocabulary/vocabulary_test.go
@@ -0,0 +1,36 @@
+// Copyright 2023 Google LLC
+//
+// 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 vocabulary
+
+import (
+	"testing"
+
+	"github.com/apigee/registry/pkg/connection/grpctest"
+	"github.com/apigee/registry/server/registry"
+)
+
+// TestMain will set up a local RegistryServer and grpc.Server for all
+// tests in this package if APG_REGISTRY_ADDRESS env var is not set
+// for the client.
+func TestMain(m *testing.M) {
+	grpctest.TestMain(m, registry.Config{})
+}
+
+func TestVocabulary(t *testing.T) {
+	command := Command()
+	if err := command.Execute(); err == nil {
+		t.Fatalf("Execute() with no args succeeded and should have failed")
+	}
+}