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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ testdata/go/project1/go.sum
testdata/go/tmp
tmp
out
.gradle

# Vim
*~
Expand Down
3 changes: 3 additions & 0 deletions common/commands/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ func reportUsageToVisibilitySystem(commandName string, serverDetails *config.Ser
IsCI: metricsData.IsCI,
CISystem: metricsData.CISystem,
IsContainer: metricsData.IsContainer,
IsAgent: metricsData.IsAgent,
Agent: metricsData.Agent,
IsInteractive: metricsData.IsInteractive,
PackageAlias: metricsData.PackageAlias,
PackageManager: metricsData.PackageManager,
}
Expand Down
13 changes: 13 additions & 0 deletions common/commands/execution_context.go
Comment thread
fluxxBot marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ var (
cachedExecutionContext ExecutionContext
)

// ResetExecutionContextForTest clears the memoized ExecutionContext so the
// next DetectExecutionContext call re-evaluates env vars. Exported for tests
// in downstream modules (e.g. jfrog-cli) that need to assert agent-detection
// behaviour against in-process command invocations after other tests in the
// same binary have already triggered memoization.
//
// Production code MUST NOT call this. Calling it concurrently with
// DetectExecutionContext is unsafe.
func ResetExecutionContextForTest() {
executionContextOnce = sync.Once{}
cachedExecutionContext = ExecutionContext{}
}

func computeExecutionContext() ExecutionContext {
ec := ExecutionContext{
IsInteractive: clientlog.IsStdOutTerminal(),
Expand Down
55 changes: 55 additions & 0 deletions common/commands/metrics_collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -984,3 +984,58 @@ func TestMetricsIntegrationFlow(t *testing.T) {
t.Error("Expected metrics to be cleared after retrieval")
}
}

// TestAgentContextEndToEnd verifies that agent/is_agent/is_interactive
// signals survive the full chain: env -> ExecutionContext -> CollectMetrics ->
// GetCollectedMetrics -> visibility.MetricsData -> commandsCountLabels -> wire JSON.
// This guards against any field being dropped at the boundaries between layers.
func TestAgentContextEndToEnd(t *testing.T) {
ClearAllMetrics()
clearAgentEnvVars(t)
t.Setenv("CURSOR_AGENT", "1")
resetExecutionContextForTest(t)

commandName := "rt_download"
flags := []string{"recursive"}

CollectMetrics(commandName, flags)

collected := GetCollectedMetrics(commandName)
if collected == nil {
t.Fatal("Expected metrics to be collected")
}
if !collected.IsAgent || collected.Agent != "cursor" {
t.Errorf("collected IsAgent/Agent wrong: IsAgent=%v Agent=%q", collected.IsAgent, collected.Agent)
}

visibilityData := &visibility.MetricsData{
Flags: collected.Flags,
Platform: collected.Platform,
Architecture: collected.Architecture,
IsCI: collected.IsCI,
CISystem: collected.CISystem,
IsContainer: collected.IsContainer,
IsAgent: collected.IsAgent,
Agent: collected.Agent,
IsInteractive: collected.IsInteractive,
PackageAlias: collected.PackageAlias,
PackageManager: collected.PackageManager,
}

metric := visibility.NewCommandsCountMetricWithEnhancedData(commandName, visibilityData)
metricJSON, err := json.Marshal(metric)
if err != nil {
t.Fatalf("json marshal failed: %v", err)
}

wire := string(metricJSON)
if !strings.Contains(wire, `"is_agent":"true"`) {
t.Errorf("wire JSON missing is_agent=true: %s", wire)
}
if !strings.Contains(wire, `"agent":"cursor"`) {
t.Errorf("wire JSON missing agent=cursor: %s", wire)
}
if !strings.Contains(wire, `"is_interactive":`) {
t.Errorf("wire JSON missing is_interactive: %s", wire)
}
}
14 changes: 14 additions & 0 deletions utils/usage/visibility/commands_count_metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ type commandsCountLabels struct {
IsCI string `json:"is_ci"`
CISystem string `json:"ci_system,omitempty"`
IsContainer string `json:"is_container"`
IsAgent string `json:"is_agent,omitempty"`
Agent string `json:"agent,omitempty"`
IsInteractive string `json:"is_interactive,omitempty"`
PackageAlias string `json:"package_alias,omitempty"`
PackageManager string `json:"package_manager,omitempty"`
}
Expand Down Expand Up @@ -79,6 +82,17 @@ func NewCommandsCountMetricWithEnhancedData(commandName string, metricsData *Met
} else {
labels.IsContainer = "false"
}
if metricsData.IsAgent {
labels.IsAgent = "true"
labels.Agent = metricsData.Agent
} else {
labels.IsAgent = "false"
}
if metricsData.IsInteractive {
labels.IsInteractive = "true"
} else {
labels.IsInteractive = "false"
}
if metricsData.PackageAlias {
labels.PackageAlias = "true"
labels.PackageManager = metricsData.PackageManager
Expand Down
70 changes: 64 additions & 6 deletions utils/usage/visibility/commands_count_metric_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,15 @@ func TestNewCommandsCountMetricWithEnhancedData(t *testing.T) {
commandName := "enhanced-test-command"

metricsData := &MetricsData{
Flags: []string{"verbose", "recursive", "threads"},
Platform: "linux",
Architecture: "amd64",
IsCI: true,
CISystem: "github_actions",
IsContainer: false,
Flags: []string{"verbose", "recursive", "threads"},
Platform: "linux",
Architecture: "amd64",
IsCI: true,
CISystem: "github_actions",
IsContainer: false,
IsAgent: true,
Agent: "cursor",
IsInteractive: false,
}

metric := NewCommandsCountMetricWithEnhancedData(commandName, metricsData)
Expand All @@ -88,6 +91,9 @@ func TestNewCommandsCountMetricWithEnhancedData(t *testing.T) {
assert.Equal(t, "true", labels.IsCI)
assert.Equal(t, "github_actions", labels.CISystem)
assert.Equal(t, "false", labels.IsContainer)
assert.Equal(t, "true", labels.IsAgent)
assert.Equal(t, "cursor", labels.Agent)
assert.Equal(t, "false", labels.IsInteractive)
}

func TestNewCommandsCountMetricWithNilEnhancedData(t *testing.T) {
Expand All @@ -113,6 +119,58 @@ func TestNewCommandsCountMetricWithNilEnhancedData(t *testing.T) {
assert.Empty(t, labels.IsCI)
assert.Empty(t, labels.CISystem)
assert.Empty(t, labels.IsContainer)
assert.Empty(t, labels.IsAgent)
assert.Empty(t, labels.Agent)
assert.Empty(t, labels.IsInteractive)
}

func TestNewCommandsCountMetricWithAgentContext(t *testing.T) {
commandName := "rt_download"

metricsData := &MetricsData{
IsAgent: true,
Agent: "cursor",
IsInteractive: true,
}

metric := NewCommandsCountMetricWithEnhancedData(commandName, metricsData)

labels, ok := metric.Labels.(*commandsCountLabels)
assert.True(t, ok, "Expected labels to be of type commandsCountLabels")

assert.Equal(t, "true", labels.IsAgent)
assert.Equal(t, "cursor", labels.Agent)
assert.Equal(t, "true", labels.IsInteractive)

metricJSON, err := json.Marshal(metric)
assert.NoError(t, err)
assert.Contains(t, string(metricJSON), `"is_agent":"true"`)
assert.Contains(t, string(metricJSON), `"agent":"cursor"`)
assert.Contains(t, string(metricJSON), `"is_interactive":"true"`)
}

func TestNewCommandsCountMetricWithoutAgentContext(t *testing.T) {
commandName := "rt_upload"

metricsData := &MetricsData{
IsAgent: false,
IsInteractive: false,
}

metric := NewCommandsCountMetricWithEnhancedData(commandName, metricsData)

labels, ok := metric.Labels.(*commandsCountLabels)
assert.True(t, ok, "Expected labels to be of type commandsCountLabels")

assert.Equal(t, "false", labels.IsAgent)
assert.Empty(t, labels.Agent)
assert.Equal(t, "false", labels.IsInteractive)

metricJSON, err := json.Marshal(metric)
assert.NoError(t, err)
assert.Contains(t, string(metricJSON), `"is_agent":"false"`)
assert.Contains(t, string(metricJSON), `"is_interactive":"false"`)
assert.NotContains(t, string(metricJSON), `"agent":`)
}

func TestMetricsDataStructure(t *testing.T) {
Expand Down
Loading