Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/sirupsen/logrus v1.10.2
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.12.1
go.uber.org/goleak v1.3.0
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
golang.org/x/tools v0.50.0
google.golang.org/protobuf v1.36.12
Expand Down
37 changes: 37 additions & 0 deletions internal/cmd/collection/testdata/duplicate_labels.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright 2026 Cockroach Labs Inc.
#
# 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.

name: query_count
enabled: true
scope: node
frequency: 10
maxresults: 100
labels: [database,database]
metrics:
- name : exec_count
kind : counter
help : statement count per application and database.
query:
SELECT
database_name as database,
sum(count) AS exec_count
FROM
crdb_internal.node_statement_statistics
WHERE
application_name NOT LIKE '$ internal-%'
GROUP BY
database_name
ORDER BY
exec_count DESC
LIMIT $1;
38 changes: 38 additions & 0 deletions internal/cmd/collection/testdata/invalid_kind.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright 2026 Cockroach Labs Inc.
#
# 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.

name: query_count
enabled: true
scope: node
frequency: 10
maxresults: 100
labels: [application,database]
metrics:
- name : exec_count
kind : bogus
help : statement count per application and database.
query:
SELECT
application_name as application,
database_name as database,
sum(count) AS exec_count
FROM
crdb_internal.node_statement_statistics
WHERE
application_name NOT LIKE '$ internal-%'
GROUP BY
application_name, database_name
ORDER BY
exec_count DESC
LIMIT $1;
14 changes: 13 additions & 1 deletion internal/cmd/collection/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,24 @@ func unmarshal(data []byte) (*store.Collection, error) {
}
metrics := make([]store.Metric, 0)
for _, m := range config.Metrics {
switch strings.ToLower(m.Kind) {
case string(store.Gauge), string(store.Counter):
default:
return nil, errors.New("invalid kind")
}
metrics = append(metrics, store.Metric{
Name: m.Name,
Kind: store.Kind(m.Kind),
Kind: store.Kind(strings.ToLower(m.Kind)),
Help: m.Help,
})
}
seenLabels := make(map[string]struct{}, len(config.Labels))
for _, l := range config.Labels {
if _, ok := seenLabels[l]; ok {
return nil, errors.Errorf("duplicate label %q", l)
}
seenLabels[l] = struct{}{}
}
var scope store.Scope
switch strings.ToLower(config.Scope) {
case "node":
Expand Down
16 changes: 16 additions & 0 deletions internal/cmd/collection/yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ import (
//go:embed testdata/invalid_scope.yaml
var invalidScope string

//go:embed testdata/invalid_kind.yaml
var invalidKind string

//go:embed testdata/duplicate_labels.yaml
var duplicateLabels string

//go:embed testdata/malformed.yaml
var malformed string

Expand Down Expand Up @@ -97,6 +103,16 @@ func TestMarshalRoundTrip(t *testing.T) {
yaml: invalidScope,
wantErr: "invalid scope",
},
{
name: "invalid kind",
yaml: invalidKind,
wantErr: "invalid kind",
},
{
name: "duplicate labels",
yaml: duplicateLabels,
wantErr: "duplicate label",
},
{
name: "unmarshal error",
yaml: malformed,
Expand Down
77 changes: 77 additions & 0 deletions internal/cmd/initialize/admin_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2026 Cockroach Labs Inc.
//
// 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.

//go:build integration

package initialize

import (
"context"
"io"
"os"
"testing"
"time"

"github.com/cockroachlabs/visus/internal/testutil"
"github.com/stretchr/testify/require"
)

// TestMain starts the shared CockroachDB cluster used by every integration
// test in this package.
func TestMain(m *testing.M) {
os.Exit(testutil.StartCluster(m))
}

// captureStdout runs f with os.Stdout redirected to a pipe and returns
// whatever f wrote to it. admin.go prints its result with fmt.Printf
// directly to os.Stdout rather than through the cobra command's output
// writer, so cmd.SetOut has nothing to capture.
func captureStdout(t *testing.T, f func()) string {
t.Helper()
r := require.New(t)

pipeReader, pipeWriter, err := os.Pipe()
r.NoError(err)
orig := os.Stdout
os.Stdout = pipeWriter
defer func() { os.Stdout = orig }()

f()

r.NoError(pipeWriter.Close())
out, err := io.ReadAll(pipeReader)
r.NoError(err)
return string(out)
}

// TestInitCommandIdempotent verifies that running the "init" CLI command
// twice against the same database succeeds both times, since it's the
// upgrade path an operator runs on every restart.
func TestInitCommandIdempotent(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
r := require.New(t)

url := testutil.PGURL().String()

for range 2 {
cmd := Command()
cmd.SetContext(ctx)
cmd.SetArgs([]string{"--url", url})
out := captureStdout(t, func() {
r.NoError(cmd.Execute())
})
r.Contains(out, "Database initialized at")
}
}
28 changes: 28 additions & 0 deletions internal/cmd/initialize/admin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2026 Cockroach Labs Inc.
//
// 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 initialize

import (
"testing"

"github.com/stretchr/testify/require"
)

// TestHelp ensures that the CLI command can be constructed and
// that all flag binding works.
func TestHelp(t *testing.T) {
r := require.New(t)
r.NoError(Command().Help())
}
2 changes: 1 addition & 1 deletion internal/cmd/scan/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func TestCommands(t *testing.T) {
mscan1, err := marshal(scan1)
r.NoError(err)
scan1Cfg := string(mscan1)
scan2 := &store.Scan{Name: "scan_02"}
scan2 := &store.Scan{Name: "scan_02", Format: store.CRDBv2}
mscan2, err := marshal(scan2)
r.NoError(err)
scan2Cfg := string(mscan2)
Expand Down
18 changes: 18 additions & 0 deletions internal/cmd/scan/testdata/invalid_format.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2026 Cockroach Labs Inc.
#
# 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.

name: scan_bad_format
enabled: true
format: bogus
path: testdata/sample.log
18 changes: 18 additions & 0 deletions internal/cmd/scan/testdata/malformed.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2026 Cockroach Labs Inc.
#
# 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.

name: scan_malformed
enabled: notabool
format: crdb-v2
path: testdata/sample.log
13 changes: 12 additions & 1 deletion internal/cmd/scan/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package scan

import (
"strings"

"github.com/cockroachdb/errors"
"github.com/cockroachlabs/visus/internal/store"
"github.com/creasty/defaults"
Expand Down Expand Up @@ -81,9 +83,18 @@ func unmarshal(data []byte) (*store.Scan, error) {
if config.Name == "" {
return nil, errors.New("name must be specified")
}
var format store.LogFormat
switch strings.ToLower(config.Format) {
case string(store.CRDBv2):
format = store.CRDBv2
case string(store.CRDBv2Auth):
format = store.CRDBv2Auth
default:
return nil, errors.New("invalid format")
}
return &store.Scan{
Enabled: config.Enabled,
Format: store.LogFormat(config.Format),
Format: format,
Path: config.Path,
Name: config.Name,
Patterns: patterns,
Expand Down
Loading