blob: 15f93a2a3196694d442a7e8acd6a55899165ab58 [file]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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 main
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/apache/beam/sdks/v2/go/container/tools"
"google.golang.org/protobuf/types/known/structpb"
)
func TestActivePidsRegistry(t *testing.T) {
// Reset active PIDs
activePids = nil
activePids = append(activePids, 101)
activePids = append(activePids, 102)
pids := getActivePids()
if len(pids) != 2 || pids[0] != 101 || pids[1] != 102 {
t.Errorf("Expected active pids [101, 102], got %v", pids)
}
unregisterPid(101)
pids = getActivePids()
if len(pids) != 1 || pids[0] != 102 {
t.Errorf("Expected active pids [102], got %v", pids)
}
unregisterPid(102)
pids = getActivePids()
if len(pids) != 0 {
t.Errorf("Expected active pids empty, got %v", pids)
}
}
func TestSetupProfilerConfig(t *testing.T) {
st, err := structpb.NewStruct(map[string]interface{}{
"options": map[string]interface{}{
"profiler_agent": "coredump",
"jobId": "test-job",
},
})
if err != nil {
t.Fatalf("Failed to create structpb: %v", err)
}
po := tools.ParseOptionsFromProto(st, "")
ctx := setupProfilerConfig(context.Background(), &tools.Logger{}, po)
pcfg := getProfilerConfig(ctx)
if pcfg == nil {
t.Fatal("ProfilerConfig was nil")
}
if pcfg.Agent != "coredump" {
t.Errorf("Expected agent coredump, got %s", pcfg.Agent)
}
}
func TestIsProfilerDisengaged(t *testing.T) {
tempDir, err := os.MkdirTemp("", "disengage_test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)
sentinelPath := filepath.Join(tempDir, "stop_sentinel")
pcfg := &ProfilerConfig{
StopSentinelPath: sentinelPath,
}
if isProfilerDisengaged(pcfg) {
t.Error("Expected profiler NOT to be disengaged before sentinel creation")
}
// Create sentinel file
if err := os.WriteFile(sentinelPath, []byte{}, 0644); err != nil {
t.Fatal(err)
}
if !isProfilerDisengaged(pcfg) {
t.Error("Expected profiler to be disengaged after sentinel creation")
}
}
func TestCreatePystackSummary(t *testing.T) {
t.Run("ExtractsGILThreadTrace", func(t *testing.T) {
output := "Thread 1 (waiting):\n" +
" File \"worker.py\", line 10, in run\n" +
"\n" +
"Thread 2 (active, Has the GIL):\n" +
" File \"main.py\", line 42, in execute\n" +
" File \"db.py\", line 5, in query\n" +
"\n" +
"Thread 3 (idle):\n" +
" File \"server.py\", line 99, in listen\n"
expected := "Thread 2 (active, Has the GIL):\n" +
" File \"main.py\", line 42, in execute\n" +
" File \"db.py\", line 5, in query\n"
result := createPystackSummary(output)
if result != expected {
t.Errorf("Expected:\n%s\nGot:\n%s", expected, result)
}
})
t.Run("FallbackSmallOutput", func(t *testing.T) {
output := "Thread 1 (waiting):\n" +
" File \"worker.py\", line 10, in run"
result := createPystackSummary(output)
if result != output {
t.Errorf("Expected identical output, got:\n%s", result)
}
})
}