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
8 changes: 8 additions & 0 deletions internal/constants/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,12 @@ var HTTPCacheDir = filepath.Join(PlanDirectory, "cache", "http")

// Platform specific output file paths
var RubyEnvOutputPath = filepath.Join(PlanDirectory, "ruby_env.json")
var JavaScriptEnvOutputPath = filepath.Join(PlanDirectory, "javascript_env.json")

// Executor constants
const (
NodeIndexPlaceholder = "{{nodeIndex}}"
WorkerIndexPlaceholder = "{{workerIndex}}"
)

var PythonEnvOutputPath = filepath.Join(PlanDirectory, "python_env.json")
6 changes: 6 additions & 0 deletions internal/discovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const (
discoveryCommandLogTruncSuffix = "..."
)

var excludedDirs = []string{"node_modules"}

type Excluder struct {
pattern string
}
Expand Down Expand Up @@ -125,6 +127,10 @@ func DiscoverTestFiles(includePattern, excludePattern string) ([]string, error)
return nil
}

if entry.IsDir() && slices.Contains(excludedDirs, entry.Name()) {
return filepath.SkipDir
}

normalizedPath := utils.NormalizePath(filePath)
if normalizedPath == "" {
return nil
Expand Down
66 changes: 66 additions & 0 deletions internal/discovery/discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"log/slog"
"os"
"path/filepath"
"runtime"
"slices"
"strings"
"testing"
Expand Down Expand Up @@ -99,6 +100,71 @@ func TestDiscoverTestFilesWithExcludeDirectory(t *testing.T) {
}
}

func TestDiscoverTestFilesSkipsNodeModules(t *testing.T) {
root := createDiscoveryFixture(t)
t.Chdir(root)

if err := os.MkdirAll(filepath.Join("node_modules", "pkg"), 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join("node_modules", "pkg", "ignored_test.rb"), []byte("test"), 0644); err != nil {
t.Fatal(err)
}

files, err := DiscoverTestFiles(filepath.Join("**", "*_test.rb"), "")
if err != nil {
t.Fatal(err)
}

expected := []string{
"test/system/payment_test.rb",
"test/system/users_test.rb",
"test/unit/order_test.rb",
"test/unit/user_test.rb",
}
if !slices.Equal(files, expected) {
t.Fatalf("expected %v, got %v", expected, files)
}
}

func TestDiscoverTestFilesSkipsUnreadableEntries(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("directory permission semantics differ on windows")
}

root := t.TempDir()
t.Chdir(root)

visibleFile := filepath.Join("test", "visible_test.rb")
if err := os.MkdirAll(filepath.Dir(visibleFile), 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(visibleFile, []byte("# test\n"), 0644); err != nil {
t.Fatal(err)
}

unreadableDir := filepath.Join("test", "unreadable")
if err := os.MkdirAll(unreadableDir, 0755); err != nil {
t.Fatal(err)
}
if err := os.Chmod(unreadableDir, 0); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
_ = os.Chmod(unreadableDir, 0755)
})

files, err := DiscoverTestFiles(filepath.Join("test", "**", "*_test.rb"), "")
if err != nil {
t.Fatal(err)
}

expected := []string{visibleFile}
if !slices.Equal(files, expected) {
t.Fatalf("expected %v, got %v", expected, files)
}
}

func TestDiscoverTestFilesNormalizesPaths(t *testing.T) {
root := createDiscoveryFixture(t)
t.Chdir(root)
Expand Down
1 change: 1 addition & 0 deletions internal/framework/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ type Framework interface {
RunTests(ctx context.Context, testFiles []string, envMap map[string]string) error
SetPlatformEnv(platformEnv map[string]string)
GetPlatformEnv() map[string]string
SupportsFullTestDiscovery() bool
}
102 changes: 102 additions & 0 deletions internal/framework/jest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package framework

import (
"context"
"errors"
"log/slog"
"maps"
"os"
"path/filepath"
"slices"
"strings"

"github.com/DataDog/ddtest/internal/discovery"
"github.com/DataDog/ddtest/internal/ext"
"github.com/DataDog/ddtest/internal/settings"
"github.com/DataDog/ddtest/internal/testoptimization"
)

const binJestPath = "node_modules/.bin/jest"

var ErrFullTestDiscoveryUnsupported = errors.New("full test discovery is not supported")

var jestTestFileExtensions = []string{"js", "jsx", "ts", "tsx", "mjs", "cjs"}

type Jest struct {
executor ext.CommandExecutor
commandOverride []string
platformEnv map[string]string
}

func NewJest() *Jest {
return &Jest{
executor: &ext.DefaultCommandExecutor{},
commandOverride: loadCommandOverride(),
platformEnv: make(map[string]string),
}
}

func (j *Jest) SetPlatformEnv(platformEnv map[string]string) {
j.platformEnv = platformEnv
}

func (j *Jest) GetPlatformEnv() map[string]string {
return j.platformEnv
}

func (j *Jest) Name() string {
return "jest"
}

// We will not be discovering tests, but test suites.
// We'll be working outside of the Node.js process
func (j *Jest) SupportsFullTestDiscovery() bool {
return false
}

func (j *Jest) TestPattern() string {
if custom := settings.GetTestsLocation(); custom != "" {
return custom
}
return "{" +
filepath.ToSlash(filepath.Join("**", "__tests__", "**", "*."+jestTestFileExtensionPattern())) + "," +
filepath.ToSlash(filepath.Join("**", "*.{spec,test}."+jestTestFileExtensionPattern())) +
"}"
}

func (j *Jest) DiscoverTests(ctx context.Context, testFiles discovery.TestFileSet) ([]testoptimization.Test, error) {
return nil, ErrFullTestDiscoveryUnsupported
}

func (j *Jest) RunTests(ctx context.Context, testFiles []string, envMap map[string]string) error {
command, baseArgs := j.getJestCommand()
args := slices.Clone(baseArgs)
args = append(args, "--runTestsByPath")
args = append(args, testFiles...)

slog.Info("Running tests with command", "command", command, "args", args)

mergedEnv := make(map[string]string)
maps.Copy(mergedEnv, j.platformEnv)
maps.Copy(mergedEnv, envMap)
return j.executor.Run(ctx, command, args, mergedEnv)
}

// Decide between user custom command, local jest binary and npx jest
func (j *Jest) getJestCommand() (string, []string) {
if len(j.commandOverride) > 0 {
return j.commandOverride[0], j.commandOverride[1:]
}

if info, err := os.Stat(binJestPath); err == nil && !info.IsDir() && info.Mode()&0111 != 0 {
slog.Debug("Using local Jest binary")
return binJestPath, []string{}
}

slog.Debug("Using npx jest for Jest commands")
return "npx", []string{"jest"}
}

func jestTestFileExtensionPattern() string {
return "{" + strings.Join(jestTestFileExtensions, ",") + "}"
}
Loading
Loading