From bb7fcd32ad8d5c735298a03ac8341d129018b036 Mon Sep 17 00:00:00 2001 From: Jared Lunde Date: Wed, 15 May 2024 17:17:50 -0600 Subject: [PATCH] python tests --- README.md | 8 -- main.go | 5 +- runtime/main.go | 4 + runtime/python.go | 4 + runtime/python_test.go | 154 +++++++++++++++++++++++ testdata/python-django/.tool-versions | 1 + testdata/python-django/Pipfile | 10 ++ testdata/python-django/Pipfile.lock | 59 +++++++++ testdata/python-django/manage.py | 0 testdata/python-pdm/.python-version | 1 + testdata/python-pdm/app.py | 0 testdata/python-pdm/pdm.lock | 0 testdata/python-poetry/app/main.py | 0 testdata/python-poetry/poetry.lock | 0 testdata/python-poetry/runtime.txt | 1 + testdata/python-pyproject/pyproject.toml | 2 + testdata/python/main.py | 0 testdata/python/requirements.txt | 0 18 files changed, 240 insertions(+), 9 deletions(-) create mode 100644 runtime/python_test.go create mode 100644 testdata/python-django/.tool-versions create mode 100644 testdata/python-django/Pipfile create mode 100644 testdata/python-django/Pipfile.lock create mode 100644 testdata/python-django/manage.py create mode 100644 testdata/python-pdm/.python-version create mode 100644 testdata/python-pdm/app.py create mode 100644 testdata/python-pdm/pdm.lock create mode 100644 testdata/python-poetry/app/main.py create mode 100644 testdata/python-poetry/poetry.lock create mode 100644 testdata/python-poetry/runtime.txt create mode 100644 testdata/python-pyproject/pyproject.toml create mode 100644 testdata/python/main.py create mode 100644 testdata/python/requirements.txt diff --git a/README.md b/README.md index 9b99c5c..ed1a1eb 100644 --- a/README.md +++ b/README.md @@ -252,15 +252,11 @@ Detected in order of precedence: [Java](https://www.java.com/) is a class-based, object-oriented programming language that is designed to have as few implementation dependencies as possible. #### Detected Files - - `build.gradle` - - `gradlew` - `pom.{xml,atom,clj,groovy,rb,scala,yml,yaml}` #### Version Detection JDK version: - `.tool-versions` - `java {VERSION}` -Gradle version: - - `.tool-versions` - `gradle {VERSION}` Maven version: - `.tool-versions` - `maven {VERSION}` @@ -269,7 +265,6 @@ Maven version: #### Build Args - `VERSION` - The version of the JDK to install (default: `17`) - - `GRADLE_VERSION` - The version of Gradle to install (default: `8`) - `MAVEN_VERSION` - The version of Maven to install (default: `3`) - `JAVA_OPTS` - The Java options to pass to the JVM (default: `-Xmx512m -Xms256m`) - `BUILD_CMD` - The command to build the project (default: best guess via source code) @@ -280,13 +275,10 @@ Maven version: #### Build Command - If Maven: `mvn -DoutputFile=target/mvn-dependency-list.log -B -DskipTests clean dependency:list install` -- If Gradle: `./gradlew clean build -x check -x test` #### Start Command - Default: `java $JAVA_OPTS -jar target/*jar` -- If Gradle: `java $JAVA_OPTS -jar $(ls -1 build/libs/*jar | grep -v plain)` - If Spring Boot: `java -Dserver.port=${PORT} $JAVA_OPTS -jar target/*jar` -- If Spring Boot w/ Gradle: `java -Dserver.port=${PORT} $JAVA_OPTS -jar $(ls -1 build/libs/*jar | grep -v plain)` --- diff --git a/main.go b/main.go index bb84e22..52fc642 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,4 @@ +// A library for auto-generating Dockerfiles from project source code. package dockerfile import ( @@ -9,7 +10,7 @@ import ( "github.com/flexstack/new-dockerfile/runtime" ) -// Creates a new Dockerfile generator. +// Creates a new Dockerfile generator. If no logger is provided, a default logger is created. func New(log ...*slog.Logger) *Dockerfile { var logger *slog.Logger @@ -50,6 +51,7 @@ func (a *Dockerfile) Write(path string) error { return nil } +// Lists all runtimes that the Dockerfile generator can auto-generate. func (a *Dockerfile) ListRuntimes() []runtime.Runtime { return []runtime.Runtime{ &runtime.Golang{Log: a.log}, @@ -67,6 +69,7 @@ func (a *Dockerfile) ListRuntimes() []runtime.Runtime { } } +// Matches the runtime of the project at the given path. func (a *Dockerfile) MatchRuntime(path string) (runtime.Runtime, error) { for _, r := range a.ListRuntimes() { if r.Match(path) { diff --git a/runtime/main.go b/runtime/main.go index 03f1b85..b98e23f 100644 --- a/runtime/main.go +++ b/runtime/main.go @@ -1,8 +1,12 @@ package runtime +// An interface that all runtimes must implement. type Runtime interface { + // Returns the name of the runtime. Name() RuntimeName + // Returns true if the runtime can be used for the given path. Match(path string) bool + // Generates a Dockerfile for the given path. GenerateDockerfile(path string) ([]byte, error) } diff --git a/runtime/python.go b/runtime/python.go index e226ee5..dbb760b 100644 --- a/runtime/python.go +++ b/runtime/python.go @@ -92,6 +92,10 @@ func (d *Python) GenerateDockerfile(path string) ([]byte, error) { if name, ok := project["name"].(string); ok { projectName = name } + } else if project, ok := pyprojectTOML["tool.poetry"].(map[string]interface{}); ok { + if name, ok := project["name"].(string); ok { + projectName = name + } } } diff --git a/runtime/python_test.go b/runtime/python_test.go new file mode 100644 index 0000000..3561730 --- /dev/null +++ b/runtime/python_test.go @@ -0,0 +1,154 @@ +package runtime_test + +import ( + "regexp" + "strings" + "testing" + + "github.com/flexstack/new-dockerfile/runtime" +) + +func TestPythonMatch(t *testing.T) { + tests := []struct { + name string + path string + expected bool + }{ + { + name: "Python project", + path: "../testdata/python", + expected: true, + }, + { + name: "Python project with django", + path: "../testdata/python-django", + expected: true, + }, + { + name: "Python project with pdm", + path: "../testdata/python-pdm", + expected: true, + }, + { + name: "Python project with poetry", + path: "../testdata/python-poetry", + expected: true, + }, + { + name: "Python project with pyproject", + path: "../testdata/python-pyproject", + expected: true, + }, + { + name: "Not a Python project", + path: "../testdata/deno", + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + python := &runtime.Python{Log: logger} + if python.Match(test.path) != test.expected { + t.Errorf("expected %v, got %v", test.expected, python.Match(test.path)) + } + }) + } +} + +func TestPythonGenerateDockerfile(t *testing.T) { + tests := []struct { + name string + path string + expected []any + }{ + { + name: "Python project", + path: "../testdata/python", + expected: []any{ + `ARG VERSION=3.12`, + `ARG INSTALL_CMD="pip install -r requirements.txt"`, + `ARG START_CMD="python main.py"`, + }, + }, + { + name: "Python project with django", + path: "../testdata/python-django", + expected: []any{ + `ARG VERSION=3.6.0`, + `ARG INSTALL_CMD="PIPENV_VENV_IN_PROJECT=1 pipenv install --deploy"`, + `ARG START_CMD="python manage.py runserver 0.0.0.0:${PORT}"`, + }, + }, + { + name: "Python project with pdm", + path: "../testdata/python-pdm", + expected: []any{ + `ARG VERSION=3.4.1`, + `ARG INSTALL_CMD="pdm install --prod"`, + `ARG START_CMD="python app.py"`, + }, + }, + { + name: "Python project with poetry", + path: "../testdata/python-poetry", + expected: []any{ + `ARG VERSION=3.8.5`, + `ARG INSTALL_CMD="poetry install --no-dev --no-interactive --no-ansi"`, + `ARG START_CMD="python app/main.py"`, + }, + }, + { + name: "Python project with pyproject", + path: "../testdata/python-pyproject", + expected: []any{ + `ARG VERSION=3.12`, + `ARG INSTALL_CMD="pip install --upgrade build setuptools \u0026\u0026 pip install .`, + `ARG START_CMD="python -m pyproject"`, + }, + }, + { + name: "Not a Python project", + path: "../testdata/deno", + expected: []any{ + `ARG VERSION=3.12`, + regexp.MustCompile(`^ARG INSTALL_CMD=$`), + regexp.MustCompile(`^ARG START_CMD=$`), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + python := &runtime.Python{Log: logger} + dockerfile, err := python.GenerateDockerfile(test.path) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + for _, line := range test.expected { + found := false + lines := strings.Split(string(dockerfile), "\n") + + for _, l := range lines { + switch v := line.(type) { + case string: + if strings.Contains(l, v) { + found = true + break + } + case *regexp.Regexp: + if v.MatchString(l) { + found = true + break + } + } + } + + if !found { + t.Errorf("expected %v, not found in %v", line, string(dockerfile)) + } + } + }) + } +} diff --git a/testdata/python-django/.tool-versions b/testdata/python-django/.tool-versions new file mode 100644 index 0000000..62e4ea0 --- /dev/null +++ b/testdata/python-django/.tool-versions @@ -0,0 +1 @@ +python 3.6.0 \ No newline at end of file diff --git a/testdata/python-django/Pipfile b/testdata/python-django/Pipfile new file mode 100644 index 0000000..b73aa42 --- /dev/null +++ b/testdata/python-django/Pipfile @@ -0,0 +1,10 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +requests = "*" +django = "*" + +[dev-packages] diff --git a/testdata/python-django/Pipfile.lock b/testdata/python-django/Pipfile.lock new file mode 100644 index 0000000..3945974 --- /dev/null +++ b/testdata/python-django/Pipfile.lock @@ -0,0 +1,59 @@ +{ + "_meta": { + "hash": { + "sha256": "bb57e0d7853b45999e47c163c46b95bc2fde31c527d8d7b5b5539dc979444a6d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "django": { + "hashes": [ + "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", + "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" + ], + "index": "pypi", + "version": "==2022.12.7" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "requests": { + "hashes": [ + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + ], + "index": "pypi", + "version": "==2.21.0" + }, + "urllib3": { + "hashes": [ + "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", + "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'", + "version": "==1.24.3" + } + }, + "develop": {} +} \ No newline at end of file diff --git a/testdata/python-django/manage.py b/testdata/python-django/manage.py new file mode 100644 index 0000000..e69de29 diff --git a/testdata/python-pdm/.python-version b/testdata/python-pdm/.python-version new file mode 100644 index 0000000..8cf6caf --- /dev/null +++ b/testdata/python-pdm/.python-version @@ -0,0 +1 @@ +3.4.1 \ No newline at end of file diff --git a/testdata/python-pdm/app.py b/testdata/python-pdm/app.py new file mode 100644 index 0000000..e69de29 diff --git a/testdata/python-pdm/pdm.lock b/testdata/python-pdm/pdm.lock new file mode 100644 index 0000000..e69de29 diff --git a/testdata/python-poetry/app/main.py b/testdata/python-poetry/app/main.py new file mode 100644 index 0000000..e69de29 diff --git a/testdata/python-poetry/poetry.lock b/testdata/python-poetry/poetry.lock new file mode 100644 index 0000000..e69de29 diff --git a/testdata/python-poetry/runtime.txt b/testdata/python-poetry/runtime.txt new file mode 100644 index 0000000..1124509 --- /dev/null +++ b/testdata/python-poetry/runtime.txt @@ -0,0 +1 @@ +python-3.8.5 \ No newline at end of file diff --git a/testdata/python-pyproject/pyproject.toml b/testdata/python-pyproject/pyproject.toml new file mode 100644 index 0000000..55ac94d --- /dev/null +++ b/testdata/python-pyproject/pyproject.toml @@ -0,0 +1,2 @@ +[project] +name = "pyproject" \ No newline at end of file diff --git a/testdata/python/main.py b/testdata/python/main.py new file mode 100644 index 0000000..e69de29 diff --git a/testdata/python/requirements.txt b/testdata/python/requirements.txt new file mode 100644 index 0000000..e69de29