Skip to content

Commit 4f9b5e4

Browse files
authored
test: add runtime tests (#5)
1 parent e318fcb commit 4f9b5e4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+1480
-65
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
node_modules
2-
dist/
2+
/dist/
33
/Dockerfile

README.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ related to this tool.
1111

1212
- [x] Automatically detect the runtime and framework used by your project
1313
- [x] Use version managers like [asdf](https://github.com/asdf-vm), nvm, rbenv, and pyenv to install the correct version of the runtime
14-
- [x] Make a best effort to detect any install, build, and run commands
14+
- [x] Make a best effort to detect any install, build, and start commands
1515
- [x] Generate a Dockerfile with sensible defaults that are configurable via [Docker Build Args](https://docs.docker.com/build/guide/build-args/)
1616
- [x] Support for a wide range of the most popular languages and frameworks including Next.js, Phoenix, Spring Boot, Django, and more
1717
- [x] Use Debian Slim as the runtime image for a smaller image size and better security, while still supporting the most common dependencies and avoiding deployment headaches caused by Alpine Linux gotchas
1818
- [x] Includes `wget` in the runtime image for adding health checks to services, e.g. `wget -nv -t1 --spider 'http://localhost:8080/healthz' || exit 1`
1919
- [x] Use multi-stage builds to reduce the size of the final image
20+
- [x] Run the application as a non-root user for better security
2021
- [x] Supports multi-platform images that run on both x86 and ARM CPU architectures
2122

2223
## Supported Runtimes
@@ -105,8 +106,8 @@ For example, if it finds a `package.json` file, it will assume the project is a
105106
a `next.config.js` file is present, in which case it will assume the project is a Next.js project.
106107

107108
From there, it will read any `.tool-versions` or other version manager files to determine the correct version
108-
of the runtime to install. It will then make a best effort to detect any install, build, and run commands.
109-
For example, a `serve`, `start`, `start:prod` command in a `package.json` file will be used as the run command.
109+
of the runtime to install. It will then make a best effort to detect any install, build, and start commands.
110+
For example, a `serve`, `start`, `start:prod` command in a `package.json` file will be used as the start command.
110111

111112
Runtimes are matched against in the order they appear when you run `new-dockerfile --runtime list`.
112113

@@ -251,15 +252,11 @@ Detected in order of precedence:
251252
[Java](https://www.java.com/) is a class-based, object-oriented programming language that is designed to have as few implementation dependencies as possible.
252253

253254
#### Detected Files
254-
- `build.gradle`
255-
- `gradlew`
256255
- `pom.{xml,atom,clj,groovy,rb,scala,yml,yaml}`
257256

258257
#### Version Detection
259258
JDK version:
260259
- `.tool-versions` - `java {VERSION}`
261-
Gradle version:
262-
- `.tool-versions` - `gradle {VERSION}`
263260
Maven version:
264261
- `.tool-versions` - `maven {VERSION}`
265262

@@ -268,7 +265,6 @@ Maven version:
268265

269266
#### Build Args
270267
- `VERSION` - The version of the JDK to install (default: `17`)
271-
- `GRADLE_VERSION` - The version of Gradle to install (default: `8`)
272268
- `MAVEN_VERSION` - The version of Maven to install (default: `3`)
273269
- `JAVA_OPTS` - The Java options to pass to the JVM (default: `-Xmx512m -Xms256m`)
274270
- `BUILD_CMD` - The command to build the project (default: best guess via source code)
@@ -279,13 +275,10 @@ Maven version:
279275

280276
#### Build Command
281277
- If Maven: `mvn -DoutputFile=target/mvn-dependency-list.log -B -DskipTests clean dependency:list install`
282-
- If Gradle: `./gradlew clean build -x check -x test`
283278

284279
#### Start Command
285280
- Default: `java $JAVA_OPTS -jar target/*jar`
286-
- If Gradle: `java $JAVA_OPTS -jar $(ls -1 build/libs/*jar | grep -v plain)`
287281
- If Spring Boot: `java -Dserver.port=${PORT} $JAVA_OPTS -jar target/*jar`
288-
- If Spring Boot w/ Gradle: `java -Dserver.port=${PORT} $JAVA_OPTS -jar $(ls -1 build/libs/*jar | grep -v plain)`
289282

290283
---
291284

@@ -300,6 +293,8 @@ Maven version:
300293

301294
#### Version Detection
302295
- `.tool-versions` - `nodejs {VERSION}`
296+
- `.nvmrc` - `v{VERSION}`
297+
- `.node-version` - `v{VERSION}`
303298

304299
#### Runtime Image
305300
`node:${VERSION}-slim`
@@ -343,6 +338,8 @@ fi
343338

344339
#### Version Detection
345340
- `.tool-versions` - `nodejs {VERSION}`
341+
- `.nvmrc` - `v{VERSION}`
342+
- `.node-version` - `v{VERSION}`
346343

347344
#### Runtime Image
348345
`node:${VERSION}-slim`
@@ -365,7 +362,7 @@ In order of precedence:
365362
#### Start Command
366363
In order of precedence:
367364
- `package.json` scripts: `"serve", "start:prod", "start:production", "start-prod", "start-production", "start"`
368-
- `package.json` scripts search for regex matching: `^.*?\bnode(mon)?\b.*?(index|main|server|client)\.js\b`
365+
- `package.json` scripts search for regex matching: `^.*?\b(ts-)?node(mon)?\b.*?(index|main|server|client)\.([cm]?[tj]s)\b`
369366
- `package.json` main/module file: `node ${mainFile}`
370367

371368
---
@@ -504,6 +501,12 @@ In order of precedence:
504501
- `TARGETARCH` - The target architecture for the build (default: `amd64`)
505502
- `BIN_NAME` - The name of the release binary (default: detected via `Cargo.toml`)
506503

504+
#### Build Command
505+
```sh
506+
if [ "${TARGETARCH}" = "amd64" ]; then rustup target add x86_64-unknown-linux-gnu; else rustup target add aarch64-unknown-linux-gnu; fi
507+
if [ "${TARGETARCH}" = "amd64" ]; then cargo zigbuild --release --target x86_64-unknown-linux-gnu; else cargo zigbuild --release --target aarch64-unknown-linux-gnu; fi
508+
```
509+
507510
#### Start Command
508511
Determined by the binary name in the `Cargo.toml` file
509512
- `["/app/app"]`

main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// A library for auto-generating Dockerfiles from project source code.
12
package dockerfile
23

34
import (
@@ -9,7 +10,7 @@ import (
910
"github.com/flexstack/new-dockerfile/runtime"
1011
)
1112

12-
// Creates a new Dockerfile generator.
13+
// Creates a new Dockerfile generator. If no logger is provided, a default logger is created.
1314
func New(log ...*slog.Logger) *Dockerfile {
1415
var logger *slog.Logger
1516

@@ -50,6 +51,7 @@ func (a *Dockerfile) Write(path string) error {
5051
return nil
5152
}
5253

54+
// Lists all runtimes that the Dockerfile generator can auto-generate.
5355
func (a *Dockerfile) ListRuntimes() []runtime.Runtime {
5456
return []runtime.Runtime{
5557
&runtime.Golang{Log: a.log},
@@ -67,6 +69,7 @@ func (a *Dockerfile) ListRuntimes() []runtime.Runtime {
6769
}
6870
}
6971

72+
// Matches the runtime of the project at the given path.
7073
func (a *Dockerfile) MatchRuntime(path string) (runtime.Runtime, error) {
7174
for _, r := range a.ListRuntimes() {
7275
if r.Match(path) {

node/.tool-versions

Lines changed: 0 additions & 1 deletion
This file was deleted.

node/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ related to this tool.
1313

1414
- [x] Automatically detect the runtime and framework used by your project
1515
- [x] Use version managers like [asdf](https://github.com/asdf-vm), nvm, rbenv, and pyenv to install the correct version of the runtime
16-
- [x] Make a best effort to detect any install, build, and run commands
16+
- [x] Make a best effort to detect any install, build, and start commands
1717
- [x] Generate a Dockerfile with sensible defaults that are configurable via [Docker Build Args](https://docs.docker.com/build/guide/build-args/)
1818
- [x] Support for a wide range of the most popular languages and frameworks including Next.js, Phoenix, Spring Boot, Django, and more
1919
- [x] Use Debian Slim as the runtime image for a smaller image size and better security, while still supporting the most common dependencies and avoiding deployment headaches caused by Alpine Linux gotchas
2020
- [x] Includes `wget` in the runtime image for adding health checks to services, e.g. `wget -nv -t1 --spider 'http://localhost:8080/healthz' || exit 1`
2121
- [x] Use multi-stage builds to reduce the size of the final image
22+
- [x] Run the application as a non-root user for better security
2223
- [x] Supports multi-platform images that run on both x86 and ARM CPU architectures
2324

2425
## Supported Runtimes
@@ -93,8 +94,8 @@ For example, if it finds a `package.json` file, it will assume the project is a
9394
a `next.config.js` file is present, in which case it will assume the project is a Next.js project.
9495

9596
From there, it will read any `.tool-versions` or other version manager files to determine the correct version
96-
of the runtime to install. It will then make a best effort to detect any install, build, and run commands.
97-
For example, a `serve`, `start`, `start:prod` command in a `package.json` file will be used as the run command.
97+
of the runtime to install. It will then make a best effort to detect any install, build, and start commands.
98+
For example, a `serve`, `start`, `start:prod` command in a `package.json` file will be used as the start command.
9899

99100
Runtimes are matched against in the order they appear when you run `new-dockerfile --runtime list`.
100101

runtime/bun.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ func (d *Bun) GenerateDockerfile(path string) ([]byte, error) {
119119
startCMD = string(startCMDJSON)
120120
}
121121

122+
if buildCMD != "" {
123+
buildCMDJSON, _ := json.Marshal(buildCMD)
124+
buildCMD = string(buildCMDJSON)
125+
}
126+
122127
var buf bytes.Buffer
123128
if err := tmpl.Option("missingkey=zero").Execute(&buf, map[string]string{
124129
"Version": *version,

runtime/bun_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package runtime_test
2+
3+
import (
4+
"regexp"
5+
"strings"
6+
"testing"
7+
8+
"github.com/flexstack/new-dockerfile/runtime"
9+
)
10+
11+
func TestBunMatch(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
path string
15+
expected bool
16+
}{
17+
{
18+
name: "Bun project",
19+
path: "../testdata/bun",
20+
expected: true,
21+
},
22+
{
23+
name: "Bun project with .ts file",
24+
path: "../testdata/bun-bunfig",
25+
expected: true,
26+
},
27+
{
28+
name: "Not a Bun project",
29+
path: "../testdata/deno",
30+
expected: false,
31+
},
32+
}
33+
34+
for _, test := range tests {
35+
t.Run(test.name, func(t *testing.T) {
36+
bun := &runtime.Bun{Log: logger}
37+
if bun.Match(test.path) != test.expected {
38+
t.Errorf("expected %v, got %v", test.expected, bun.Match(test.path))
39+
}
40+
})
41+
}
42+
}
43+
44+
func TestBunGenerateDockerfile(t *testing.T) {
45+
tests := []struct {
46+
name string
47+
path string
48+
expected []any
49+
}{
50+
{
51+
name: "Bun project",
52+
path: "../testdata/bun",
53+
expected: []any{`ARG VERSION=1`, `ARG INSTALL_CMD="bun install"`, regexp.MustCompile(`^ARG BUILD_CMD=$`), `ARG START_CMD="bun index.ts"`},
54+
},
55+
{
56+
name: "Bun project with .ts file",
57+
path: "../testdata/bun-bunfig",
58+
expected: []any{`ARG VERSION=1.1.4`, `ARG INSTALL_CMD="bun install"`, `ARG BUILD_CMD="bun run build:prod"`, `ARG START_CMD="bun run start:production"`},
59+
},
60+
{
61+
name: "Not a Bun project",
62+
path: "../testdata/deno",
63+
expected: []any{`ARG VERSION=1`, regexp.MustCompile(`^ARG INSTALL_CMD="bun install"`), regexp.MustCompile(`^ARG BUILD_CMD=$`), regexp.MustCompile(`^ARG START_CMD=$`)},
64+
},
65+
}
66+
67+
for _, test := range tests {
68+
t.Run(test.name, func(t *testing.T) {
69+
bun := &runtime.Bun{Log: logger}
70+
dockerfile, err := bun.GenerateDockerfile(test.path)
71+
if err != nil {
72+
t.Errorf("unexpected error: %v", err)
73+
}
74+
75+
for _, line := range test.expected {
76+
found := false
77+
lines := strings.Split(string(dockerfile), "\n")
78+
79+
for _, l := range lines {
80+
switch v := line.(type) {
81+
case string:
82+
if strings.Contains(l, v) {
83+
found = true
84+
break
85+
}
86+
case *regexp.Regexp:
87+
if v.MatchString(l) {
88+
found = true
89+
break
90+
}
91+
}
92+
}
93+
94+
if !found {
95+
t.Errorf("expected %v, not found in %v", line, string(dockerfile))
96+
}
97+
}
98+
})
99+
}
100+
}

runtime/deno_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"github.com/flexstack/new-dockerfile/runtime"
99
)
1010

11-
func TestRuntimeMatch(t *testing.T) {
11+
func TestDenoMatch(t *testing.T) {
1212
tests := []struct {
1313
name string
1414
path string
@@ -41,7 +41,7 @@ func TestRuntimeMatch(t *testing.T) {
4141
}
4242
}
4343

44-
func TestRuntimeGenerateDockerfile(t *testing.T) {
44+
func TestDenoGenerateDockerfile(t *testing.T) {
4545
tests := []struct {
4646
name string
4747
path string

runtime/elixir.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,10 @@ func isPhoenixProject(path string) bool {
269269
}
270270

271271
func findBinName(path string) (string, error) {
272+
if _, err := os.Stat(filepath.Join(path, "mix.exs")); err != nil {
273+
return "", nil
274+
}
275+
272276
configFile, err := os.Open(filepath.Join(path, "mix.exs"))
273277
if err != nil {
274278
return "", err

0 commit comments

Comments
 (0)