Compare commits

...

8 Commits

Author SHA1 Message Date
Ilya Kuznetsov 530a2a9bab
fix: Skipping path translation tests for windows 2024-11-19 13:33:02 +01:00
Ilya Kuznetsov 5d04569112
fix: Windows tests 2024-11-19 13:15:52 +01:00
Ilya Kuznetsov 8ee8de554b
test: Added path translation test cases for source-linked 2024-11-19 12:46:31 +01:00
Ilya Kuznetsov 5d09070fcb
fix: Cleanup 2024-11-19 11:50:28 +01:00
Ilya Kuznetsov 234d971790
fix: Cleanup in test 2024-11-19 11:47:57 +01:00
Ilya Kuznetsov 8c8fb35861
fix: Add explicit warning when using python wheel wrappers with source-linked deployment 2024-11-19 10:46:40 +01:00
Ilya Kuznetsov aeb9813d25
feat: Use path translations instead of overriding config 2024-11-19 10:40:16 +01:00
Ilya Kuznetsov 518aa14b64
Revert "fix: Skip permissions set and check for in-place"
This reverts commit 53e1f6df6a.
2024-11-18 22:32:36 +01:00
13 changed files with 227 additions and 78 deletions

View File

@ -32,10 +32,6 @@ func (r ReadOnlyBundle) SyncRoot() vfs.Path {
return r.b.SyncRoot return r.b.SyncRoot
} }
func (r ReadOnlyBundle) SyncRootPath() string {
return r.b.SyncRootPath
}
func (r ReadOnlyBundle) WorkspaceClient() *databricks.WorkspaceClient { func (r ReadOnlyBundle) WorkspaceClient() *databricks.WorkspaceClient {
return r.b.WorkspaceClient() return r.b.WorkspaceClient()
} }

View File

@ -223,11 +223,8 @@ func (m *applyPresets) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnos
} }
if config.IsExplicitlyEnabled((b.Config.Presets.SourceLinkedDeployment)) { if config.IsExplicitlyEnabled((b.Config.Presets.SourceLinkedDeployment)) {
root := b.SyncRootPath isDatabricksWorkspace := dbr.RunsOnRuntime(ctx) && strings.HasPrefix(b.SyncRootPath, "/Workspace/")
isInWorkspace := strings.HasPrefix(root, "/Workspace/") if !isDatabricksWorkspace {
if isInWorkspace && dbr.RunsOnRuntime(ctx) {
b.Config.Workspace.FilePath = root
} else {
disabled := false disabled := false
b.Config.Presets.SourceLinkedDeployment = &disabled b.Config.Presets.SourceLinkedDeployment = &disabled
diags = diags.Extend(diag.Warningf("source-linked deployment is available only in the Databricks Workspace")) diags = diags.Extend(diag.Warningf("source-linked deployment is available only in the Databricks Workspace"))

View File

@ -10,7 +10,6 @@ import (
"github.com/databricks/cli/bundle/config/mutator" "github.com/databricks/cli/bundle/config/mutator"
"github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/config/resources"
"github.com/databricks/cli/libs/dbr" "github.com/databricks/cli/libs/dbr"
"github.com/databricks/cli/libs/vfs"
"github.com/databricks/databricks-sdk-go/service/catalog" "github.com/databricks/databricks-sdk-go/service/catalog"
"github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/jobs"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -48,7 +47,6 @@ func TestApplyPresetsPrefix(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
b := &bundle.Bundle{ b := &bundle.Bundle{
SyncRoot: vfs.MustNew(t.TempDir()),
Config: config.Root{ Config: config.Root{
Resources: config.Resources{ Resources: config.Resources{
Jobs: map[string]*resources.Job{ Jobs: map[string]*resources.Job{
@ -377,75 +375,63 @@ func TestApplyPresetsSourceLinkedDeployment(t *testing.T) {
testContext := context.Background() testContext := context.Background()
enabled := true enabled := true
disabled := false disabled := false
remotePath := "/Users/files"
workspacePath := "/Workspace/user.name@company.com" workspacePath := "/Workspace/user.name@company.com"
tests := []struct { tests := []struct {
bundlePath string bundlePath string
ctx context.Context ctx context.Context
name string name string
initialValue *bool initialValue *bool
expectedValue *bool expectedValue *bool
expectedFilePath string expectedWarning string
expectedWarning string
}{ }{
{ {
name: "preset enabled, bundle in Workspace, databricks runtime", name: "preset enabled, bundle in Workspace, databricks runtime",
bundlePath: workspacePath, bundlePath: workspacePath,
ctx: dbr.MockRuntime(testContext, true), ctx: dbr.MockRuntime(testContext, true),
initialValue: &enabled, initialValue: &enabled,
expectedValue: &enabled, expectedValue: &enabled,
expectedFilePath: workspacePath,
expectedWarning: "",
}, },
{ {
name: "preset enabled, bundle not in Workspace, databricks runtime", name: "preset enabled, bundle not in Workspace, databricks runtime",
bundlePath: "/Users/user.name@company.com", bundlePath: "/Users/user.name@company.com",
ctx: dbr.MockRuntime(testContext, true), ctx: dbr.MockRuntime(testContext, true),
initialValue: &enabled, initialValue: &enabled,
expectedValue: &disabled, expectedValue: &disabled,
expectedFilePath: remotePath, expectedWarning: "source-linked deployment is available only in the Databricks Workspace",
expectedWarning: "source-linked deployment is available only in the Databricks Workspace",
}, },
{ {
name: "preset enabled, bundle in Workspace, not databricks runtime", name: "preset enabled, bundle in Workspace, not databricks runtime",
bundlePath: workspacePath, bundlePath: workspacePath,
ctx: dbr.MockRuntime(testContext, false), ctx: dbr.MockRuntime(testContext, false),
initialValue: &enabled, initialValue: &enabled,
expectedValue: &disabled, expectedValue: &disabled,
expectedFilePath: remotePath, expectedWarning: "source-linked deployment is available only in the Databricks Workspace",
expectedWarning: "source-linked deployment is available only in the Databricks Workspace",
}, },
{ {
name: "preset disabled, bundle in Workspace, databricks runtime", name: "preset disabled, bundle in Workspace, databricks runtime",
bundlePath: workspacePath, bundlePath: workspacePath,
ctx: dbr.MockRuntime(testContext, true), ctx: dbr.MockRuntime(testContext, true),
initialValue: &disabled, initialValue: &disabled,
expectedValue: &disabled, expectedValue: &disabled,
expectedFilePath: remotePath,
}, },
{ {
name: "preset nil, bundle in Workspace, databricks runtime", name: "preset nil, bundle in Workspace, databricks runtime",
bundlePath: workspacePath, bundlePath: workspacePath,
ctx: dbr.MockRuntime(testContext, true), ctx: dbr.MockRuntime(testContext, true),
initialValue: nil, initialValue: nil,
expectedValue: nil, expectedValue: nil,
expectedFilePath: remotePath,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
b := &bundle.Bundle{ b := &bundle.Bundle{
SyncRoot: vfs.MustNew(tt.bundlePath),
SyncRootPath: tt.bundlePath, SyncRootPath: tt.bundlePath,
Config: config.Root{ Config: config.Root{
Presets: config.Presets{ Presets: config.Presets{
SourceLinkedDeployment: tt.initialValue, SourceLinkedDeployment: tt.initialValue,
}, },
Workspace: config.Workspace{
FilePath: remotePath,
},
}, },
} }
@ -458,7 +444,6 @@ func TestApplyPresetsSourceLinkedDeployment(t *testing.T) {
require.Equal(t, tt.expectedWarning, diags[0].Summary) require.Equal(t, tt.expectedWarning, diags[0].Summary)
} }
require.Equal(t, tt.expectedFilePath, b.Config.Workspace.FilePath)
require.Equal(t, tt.expectedValue, b.Config.Presets.SourceLinkedDeployment) require.Equal(t, tt.expectedValue, b.Config.Presets.SourceLinkedDeployment)
}) })
} }

View File

@ -59,8 +59,7 @@ func transformDevelopmentMode(ctx context.Context, b *bundle.Bundle) {
} }
if !config.IsExplicitlyDisabled(t.SourceLinkedDeployment) { if !config.IsExplicitlyDisabled(t.SourceLinkedDeployment) {
root := b.SyncRootPath isInWorkspace := strings.HasPrefix(b.SyncRootPath, "/Workspace/")
isInWorkspace := strings.HasPrefix(root, "/Workspace/")
if isInWorkspace && dbr.RunsOnRuntime(ctx) { if isInWorkspace && dbr.RunsOnRuntime(ctx) {
enabled := true enabled := true
t.SourceLinkedDeployment = &enabled t.SourceLinkedDeployment = &enabled

View File

@ -524,14 +524,12 @@ func TestSourceLinkedDeploymentEnabled(t *testing.T) {
b, diags := processSourceLinkedBundle(t, true) b, diags := processSourceLinkedBundle(t, true)
require.NoError(t, diags.Error()) require.NoError(t, diags.Error())
assert.True(t, *b.Config.Presets.SourceLinkedDeployment) assert.True(t, *b.Config.Presets.SourceLinkedDeployment)
assert.Equal(t, b.Config.Workspace.FilePath, b.SyncRootPath)
} }
func TestSourceLinkedDeploymentDisabled(t *testing.T) { func TestSourceLinkedDeploymentDisabled(t *testing.T) {
b, diags := processSourceLinkedBundle(t, false) b, diags := processSourceLinkedBundle(t, false)
require.NoError(t, diags.Error()) require.NoError(t, diags.Error())
assert.False(t, *b.Config.Presets.SourceLinkedDeployment) assert.False(t, *b.Config.Presets.SourceLinkedDeployment)
assert.NotEqual(t, b.Config.Workspace.FilePath, b.SyncRootPath)
} }
func processSourceLinkedBundle(t *testing.T, presetEnabled bool) (*bundle.Bundle, diag.Diagnostics) { func processSourceLinkedBundle(t *testing.T, presetEnabled bool) (*bundle.Bundle, diag.Diagnostics) {
@ -542,7 +540,6 @@ func processSourceLinkedBundle(t *testing.T, presetEnabled bool) (*bundle.Bundle
b := mockBundle(config.Development) b := mockBundle(config.Development)
workspacePath := "/Workspace/lennart@company.com/" workspacePath := "/Workspace/lennart@company.com/"
b.SyncRoot = vfs.MustNew(workspacePath)
b.SyncRootPath = workspacePath b.SyncRootPath = workspacePath
b.Config.Presets.SourceLinkedDeployment = &presetEnabled b.Config.Presets.SourceLinkedDeployment = &presetEnabled

View File

@ -11,6 +11,7 @@ import (
"strings" "strings"
"github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/config"
"github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/dyn"
"github.com/databricks/cli/libs/notebook" "github.com/databricks/cli/libs/notebook"
@ -103,8 +104,13 @@ func (t *translateContext) rewritePath(
return fmt.Errorf("path %s is not contained in sync root path", localPath) return fmt.Errorf("path %s is not contained in sync root path", localPath)
} }
// Prefix remote path with its remote root path. var workspacePath string
remotePath := path.Join(t.b.Config.Workspace.FilePath, filepath.ToSlash(localRelPath)) if config.IsExplicitlyEnabled(t.b.Config.Presets.SourceLinkedDeployment) {
workspacePath = t.b.SyncRootPath
} else {
workspacePath = t.b.Config.Workspace.FilePath
}
remotePath := path.Join(workspacePath, filepath.ToSlash(localRelPath))
// Convert local path into workspace path via specified function. // Convert local path into workspace path via specified function.
interp, err := fn(*p, localPath, localRelPath, remotePath) interp, err := fn(*p, localPath, localRelPath, remotePath)

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"testing" "testing"
@ -787,3 +788,163 @@ func TestTranslatePathWithComplexVariables(t *testing.T) {
b.Config.Resources.Jobs["job"].Tasks[0].Libraries[0].Whl, b.Config.Resources.Jobs["job"].Tasks[0].Libraries[0].Whl,
) )
} }
func TestTranslatePathsWithSourceLinkedDeployment(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("this test is not applicable on Windows because source-linked mode works only in the Databricks Workspace")
}
dir := t.TempDir()
touchNotebookFile(t, filepath.Join(dir, "my_job_notebook.py"))
touchNotebookFile(t, filepath.Join(dir, "my_pipeline_notebook.py"))
touchEmptyFile(t, filepath.Join(dir, "my_python_file.py"))
touchEmptyFile(t, filepath.Join(dir, "dist", "task.jar"))
touchEmptyFile(t, filepath.Join(dir, "requirements.txt"))
enabled := true
b := &bundle.Bundle{
SyncRootPath: dir,
SyncRoot: vfs.MustNew(dir),
Config: config.Root{
Workspace: config.Workspace{
FilePath: "/bundle",
},
Resources: config.Resources{
Jobs: map[string]*resources.Job{
"job": {
JobSettings: &jobs.JobSettings{
Tasks: []jobs.Task{
{
NotebookTask: &jobs.NotebookTask{
NotebookPath: "my_job_notebook.py",
},
Libraries: []compute.Library{
{Whl: "./dist/task.whl"},
},
},
{
NotebookTask: &jobs.NotebookTask{
NotebookPath: "/Users/jane.doe@databricks.com/absolute_remote.py",
},
},
{
NotebookTask: &jobs.NotebookTask{
NotebookPath: "my_job_notebook.py",
},
Libraries: []compute.Library{
{Requirements: "requirements.txt"},
},
},
{
SparkPythonTask: &jobs.SparkPythonTask{
PythonFile: "my_python_file.py",
},
},
{
SparkJarTask: &jobs.SparkJarTask{
MainClassName: "HelloWorld",
},
Libraries: []compute.Library{
{Jar: "./dist/task.jar"},
},
},
{
SparkJarTask: &jobs.SparkJarTask{
MainClassName: "HelloWorldRemote",
},
Libraries: []compute.Library{
{Jar: "dbfs:/bundle/dist/task_remote.jar"},
},
},
},
},
},
},
Pipelines: map[string]*resources.Pipeline{
"pipeline": {
PipelineSpec: &pipelines.PipelineSpec{
Libraries: []pipelines.PipelineLibrary{
{
Notebook: &pipelines.NotebookLibrary{
Path: "my_pipeline_notebook.py",
},
},
{
Notebook: &pipelines.NotebookLibrary{
Path: "/Users/jane.doe@databricks.com/absolute_remote.py",
},
},
{
File: &pipelines.FileLibrary{
Path: "my_python_file.py",
},
},
},
},
},
},
},
Presets: config.Presets{
SourceLinkedDeployment: &enabled,
},
},
}
bundletest.SetLocation(b, ".", []dyn.Location{{File: filepath.Join(dir, "resource.yml")}})
diags := bundle.Apply(context.Background(), b, mutator.TranslatePaths())
require.NoError(t, diags.Error())
// updated to source path
assert.Equal(
t,
filepath.Join(dir, "my_job_notebook"),
b.Config.Resources.Jobs["job"].Tasks[0].NotebookTask.NotebookPath,
)
assert.Equal(
t,
filepath.Join(dir, "requirements.txt"),
b.Config.Resources.Jobs["job"].Tasks[2].Libraries[0].Requirements,
)
assert.Equal(
t,
filepath.Join(dir, "my_python_file.py"),
b.Config.Resources.Jobs["job"].Tasks[3].SparkPythonTask.PythonFile,
)
assert.Equal(
t,
filepath.Join(dir, "my_pipeline_notebook"),
b.Config.Resources.Pipelines["pipeline"].Libraries[0].Notebook.Path,
)
assert.Equal(
t,
filepath.Join(dir, "my_python_file.py"),
b.Config.Resources.Pipelines["pipeline"].Libraries[2].File.Path,
)
// left as is
assert.Equal(
t,
filepath.Join("dist", "task.whl"),
b.Config.Resources.Jobs["job"].Tasks[0].Libraries[0].Whl,
)
assert.Equal(
t,
"/Users/jane.doe@databricks.com/absolute_remote.py",
b.Config.Resources.Jobs["job"].Tasks[1].NotebookTask.NotebookPath,
)
assert.Equal(
t,
filepath.Join("dist", "task.jar"),
b.Config.Resources.Jobs["job"].Tasks[4].Libraries[0].Jar,
)
assert.Equal(
t,
"dbfs:/bundle/dist/task_remote.jar",
b.Config.Resources.Jobs["job"].Tasks[5].Libraries[0].Jar,
)
assert.Equal(
t,
"/Users/jane.doe@databricks.com/absolute_remote.py",
b.Config.Resources.Pipelines["pipeline"].Libraries[1].Notebook.Path,
)
}

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"path" "path"
"strings"
"github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/bundle/libraries"
@ -31,9 +30,6 @@ func (f *folderPermissions) Apply(ctx context.Context, b bundle.ReadOnlyBundle)
g, ctx := errgroup.WithContext(ctx) g, ctx := errgroup.WithContext(ctx)
results := make([]diag.Diagnostics, len(bundlePaths)) results := make([]diag.Diagnostics, len(bundlePaths))
for i, p := range bundlePaths { for i, p := range bundlePaths {
if strings.HasPrefix(p, b.SyncRootPath()) {
continue
}
g.Go(func() error { g.Go(func() error {
results[i] = checkFolderPermission(ctx, b, p) results[i] = checkFolderPermission(ctx, b, p)
return nil return nil

View File

@ -18,7 +18,6 @@ import (
func TestFolderPermissionsInheritedWhenRootPathDoesNotExist(t *testing.T) { func TestFolderPermissionsInheritedWhenRootPathDoesNotExist(t *testing.T) {
b := &bundle.Bundle{ b := &bundle.Bundle{
SyncRootPath: t.TempDir(),
Config: config.Root{ Config: config.Root{
Workspace: config.Workspace{ Workspace: config.Workspace{
RootPath: "/Workspace/Users/foo@bar.com", RootPath: "/Workspace/Users/foo@bar.com",
@ -78,7 +77,6 @@ func TestFolderPermissionsInheritedWhenRootPathDoesNotExist(t *testing.T) {
func TestValidateFolderPermissionsFailsOnMissingBundlePermission(t *testing.T) { func TestValidateFolderPermissionsFailsOnMissingBundlePermission(t *testing.T) {
b := &bundle.Bundle{ b := &bundle.Bundle{
SyncRootPath: t.TempDir(),
Config: config.Root{ Config: config.Root{
Workspace: config.Workspace{ Workspace: config.Workspace{
RootPath: "/Workspace/Users/foo@bar.com", RootPath: "/Workspace/Users/foo@bar.com",
@ -131,7 +129,6 @@ func TestValidateFolderPermissionsFailsOnMissingBundlePermission(t *testing.T) {
func TestValidateFolderPermissionsFailsOnPermissionMismatch(t *testing.T) { func TestValidateFolderPermissionsFailsOnPermissionMismatch(t *testing.T) {
b := &bundle.Bundle{ b := &bundle.Bundle{
SyncRootPath: t.TempDir(),
Config: config.Root{ Config: config.Root{
Workspace: config.Workspace{ Workspace: config.Workspace{
RootPath: "/Workspace/Users/foo@bar.com", RootPath: "/Workspace/Users/foo@bar.com",
@ -177,7 +174,6 @@ func TestValidateFolderPermissionsFailsOnPermissionMismatch(t *testing.T) {
func TestValidateFolderPermissionsFailsOnNoRootFolder(t *testing.T) { func TestValidateFolderPermissionsFailsOnNoRootFolder(t *testing.T) {
b := &bundle.Bundle{ b := &bundle.Bundle{
SyncRootPath: t.TempDir(),
Config: config.Root{ Config: config.Root{
Workspace: config.Workspace{ Workspace: config.Workspace{
RootPath: "/NotExisting", RootPath: "/NotExisting",

View File

@ -3,7 +3,6 @@ package permissions
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/bundle/libraries"
@ -60,9 +59,6 @@ func giveAccessForWorkspaceRoot(ctx context.Context, b *bundle.Bundle) error {
g, ctx := errgroup.WithContext(ctx) g, ctx := errgroup.WithContext(ctx)
for _, p := range bundlePaths { for _, p := range bundlePaths {
if strings.HasPrefix(p, b.SyncRootPath) {
continue
}
g.Go(func() error { g.Go(func() error {
return setPermissions(ctx, w, p, permissions) return setPermissions(ctx, w, p, permissions)
}) })

View File

@ -19,7 +19,6 @@ import (
func TestApplyWorkspaceRootPermissions(t *testing.T) { func TestApplyWorkspaceRootPermissions(t *testing.T) {
b := &bundle.Bundle{ b := &bundle.Bundle{
SyncRootPath: t.TempDir(),
Config: config.Root{ Config: config.Root{
Workspace: config.Workspace{ Workspace: config.Workspace{
RootPath: "/Users/foo@bar.com", RootPath: "/Users/foo@bar.com",
@ -80,7 +79,6 @@ func TestApplyWorkspaceRootPermissions(t *testing.T) {
func TestApplyWorkspaceRootPermissionsForAllPaths(t *testing.T) { func TestApplyWorkspaceRootPermissionsForAllPaths(t *testing.T) {
b := &bundle.Bundle{ b := &bundle.Bundle{
SyncRootPath: t.TempDir(),
Config: config.Root{ Config: config.Root{
Workspace: config.Workspace{ Workspace: config.Workspace{
RootPath: "/Some/Root/Path", RootPath: "/Some/Root/Path",

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/config"
"github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/bundle/libraries"
"github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/log"
@ -22,6 +23,9 @@ func WrapperWarning() bundle.Mutator {
func (m *wrapperWarning) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { func (m *wrapperWarning) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
if isPythonWheelWrapperOn(b) { if isPythonWheelWrapperOn(b) {
if config.IsExplicitlyEnabled(b.Config.Presets.SourceLinkedDeployment) {
return diag.Warningf("Python wheel notebook wrapper is not available when using source-linked deployment mode. You can disable this mode by setting 'presets.source_linked_deployment: false'")
}
return nil return nil
} }

View File

@ -335,6 +335,24 @@ func TestNoWarningWhenPythonWheelWrapperIsOn(t *testing.T) {
require.NoError(t, diags.Error()) require.NoError(t, diags.Error())
} }
func TestPythonWheelWithSourceLinkedDeployment(t *testing.T) {
enabled := true
b := &bundle.Bundle{
Config: config.Root{
Experimental: &config.Experimental{
PythonWheelWrapper: true,
},
Presets: config.Presets{
SourceLinkedDeployment: &enabled,
},
},
}
diags := bundle.Apply(context.Background(), b, WrapperWarning())
require.NoError(t, diags.Error())
require.Contains(t, diags[0].Summary, "Python wheel notebook wrapper is not available when using source-linked deployment mode")
}
func TestSparkVersionLowerThanExpected(t *testing.T) { func TestSparkVersionLowerThanExpected(t *testing.T) {
testCases := map[string]bool{ testCases := map[string]bool{
"13.1.x-scala2.12": false, "13.1.x-scala2.12": false,