-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Expand file tree
/
Copy pathspring_boot_test.go
More file actions
294 lines (258 loc) · 12.9 KB
/
Copy pathspring_boot_test.go
File metadata and controls
294 lines (258 loc) · 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
package integration_test
import (
"path/filepath"
"testing"
"github.com/cloudfoundry/switchblade"
"github.com/cloudfoundry/switchblade/matchers"
"github.com/sclevine/spec"
. "github.com/onsi/gomega"
)
func testSpringBoot(platform switchblade.Platform, fixtures string) func(*testing.T, spec.G, spec.S) {
return func(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect
Eventually = NewWithT(t).Eventually
name string
)
it.Before(func() {
var err error
name, err = switchblade.RandomName()
Expect(err).NotTo(HaveOccurred())
})
it.After(func() {
if t.Failed() && name != "" {
t.Logf("❌ FAILED TEST - App/Container: %s", name)
t.Logf(" Platform: %s", settings.Platform)
}
if name != "" && (!settings.KeepFailedContainers || !t.Failed()) {
Expect(platform.Delete.Execute(name)).To(Succeed())
}
})
context("with a Spring Boot application", func() {
it("successfully deploys and runs", func() {
deployment, logs, err := platform.Deploy.
WithEnv(map[string]string{
"BP_JAVA_VERSION": "11",
}).
Execute(name, filepath.Join(fixtures, "containers", "spring_boot_staged"))
Expect(err).NotTo(HaveOccurred(), logs.String)
Expect(logs.String()).To(ContainSubstring("Java Buildpack"))
Eventually(deployment).Should(matchers.Serve(Not(BeEmpty())))
})
})
context("with embedded Tomcat", func() {
it("starts successfully", func() {
deployment, logs, err := platform.Deploy.
WithEnv(map[string]string{
"BP_JAVA_VERSION": "11",
}).
Execute(name, filepath.Join(fixtures, "containers", "spring_boot_staged"))
Expect(err).NotTo(HaveOccurred(), logs.String)
Expect(logs.String()).To(Or(
ContainSubstring("Tomcat"),
ContainSubstring("JRE"),
))
Eventually(deployment).Should(matchers.Serve(Not(BeEmpty())))
})
})
context("with Java CFEnv", func() {
it("includes java-cfenv when Spring Boot is detected", func() {
deployment, logs, err := platform.Deploy.
WithEnv(map[string]string{
"BP_JAVA_VERSION": "11",
"JBP_CONFIG_JAVA_CF_ENV": "{enabled: true}",
}).
Execute(name, filepath.Join(fixtures, "containers", "spring_boot_staged"))
Expect(err).NotTo(HaveOccurred(), logs.String)
// Fixture is not a Spring3 app, so Java CF Env detect should not pass
Expect(logs.String()).NotTo(ContainSubstring("Java CF Env"))
Eventually(deployment).Should(matchers.Serve(Not(BeEmpty())))
})
})
context("with JAVA_OPTS configuration", func() {
it("applies configured JAVA_OPTS with from_environment=false and verifies at runtime", func() {
deployment, logs, err := platform.Deploy.
WithEnv(map[string]string{
"BP_JAVA_VERSION": "17",
// Reduce memory settings to fit within 1G limit (v4 calculator)
"JBP_CONFIG_JAVA_OPTS": `[from_environment: false, java_opts: '-Xmx256M -Xms128M -Xss512k -XX:ReservedCodeCacheSize=120M -XX:MetaspaceSize=78643K -XX:MaxMetaspaceSize=157286K -DoptionKey=optionValue']`,
}).
Execute(name, filepath.Join(fixtures, "containers", "spring_boot_staged"))
Expect(err).NotTo(HaveOccurred(), logs.String)
// Verify buildpack detected and configured Java Opts
Expect(logs.String()).To(ContainSubstring("Java Opts"))
Expect(logs.String()).To(ContainSubstring("Adding configured JAVA_OPTS"))
Expect(logs.String()).To(ContainSubstring("-Xmx256M"))
// Verify Container Security Provider is configured (should add its opts)
Expect(logs.String()).To(ContainSubstring("Container Security Provider"))
// Verify configured opts are actually applied at runtime
Eventually(deployment).Should(matchers.Serve(And(
ContainSubstring("-Xmx256M"),
ContainSubstring("-Xms128M"),
ContainSubstring("-Xss512k"),
ContainSubstring("-XX:ReservedCodeCacheSize=120M"),
ContainSubstring("-XX:MetaspaceSize=78643K"),
ContainSubstring("-XX:MaxMetaspaceSize=157286K"),
ContainSubstring("optionKey=optionValue"), // Custom system property
)).WithEndpoint("/jvm-args"))
// Verify Container Security Provider opts use runtime paths ($DEPS_DIR), not staging paths
Eventually(deployment).Should(matchers.Serve(And(
Not(ContainSubstring("/tmp/contents")), // Should NOT have staging path
ContainSubstring("-Djava.security.properties="),
)).WithEndpoint("/jvm-args"))
})
it("applies configured JAVA_OPTS with from_environment=true and preserves user opts", func() {
deployment, logs, err := platform.Deploy.
WithEnv(map[string]string{
"BP_JAVA_VERSION": "17",
// Reduce memory settings to fit within 1G limit (v4 calculator)
"JBP_CONFIG_JAVA_OPTS": `{from_environment: true, java_opts: ["-Xmx384M", "-XX:ReservedCodeCacheSize=120M", "-Xss512k", "-DconfiguredProperty=fromConfig"]}`,
"JAVA_OPTS": "-DuserProperty=fromUser",
}).
Execute(name, filepath.Join(fixtures, "containers", "spring_boot_staged"))
Expect(err).NotTo(HaveOccurred(), logs.String)
// Verify buildpack detected and configured Java Opts
Expect(logs.String()).To(ContainSubstring("Java Opts"))
Expect(logs.String()).To(ContainSubstring("Adding configured JAVA_OPTS"))
// Verify application starts successfully
Eventually(deployment).Should(matchers.Serve(ContainSubstring("Hello from Spring Boot")))
})
it("applies only configured JAVA_OPTS with from_environment=false and ignores user opts", func() {
deployment, logs, err := platform.Deploy.
WithEnv(map[string]string{
"BP_JAVA_VERSION": "17",
// Reduce memory settings to fit within 1G limit (v4 calculator)
"JBP_CONFIG_JAVA_OPTS": `{from_environment: false, java_opts: ["-Xmx384M", "-XX:ReservedCodeCacheSize=120M", "-Xss512k", "-DconfiguredProperty=fromConfig"]}`,
"JAVA_OPTS": "-DuserProperty=shouldBeIgnored",
}).
Execute(name, filepath.Join(fixtures, "containers", "spring_boot_staged"))
Expect(err).NotTo(HaveOccurred(), logs.String)
// Verify buildpack detected and configured Java Opts
Expect(logs.String()).To(ContainSubstring("Java Opts"))
// Verify application starts successfully
Eventually(deployment).Should(matchers.Serve(ContainSubstring("Hello from Spring Boot")))
})
// Regression tests for #1301: the start command no longer uses `eval`, and
// JAVA_OPTS is assembled by a pure-bash expander and tokenized at launch by
// the shell-free javaexec launcher. A user-supplied JAVA_OPTS value must
// therefore reach the JVM as literal text — command substitutions are never
// executed, and globs/cron stars/shell metacharacters are never expanded or
// interpreted as operators. These drive the value through the user JAVA_OPTS
// env path (from_environment: true; memory comes from the configured opts),
// then read the JVM's actual received value back via the fixture's /jvm-args
// endpoint (System.getProperty("userProperty")).
memoryOpts := `java_opts: ["-Xmx256M", "-Xms128M", "-Xss512k", "-XX:ReservedCodeCacheSize=120M", "-XX:MetaspaceSize=78643K", "-XX:MaxMetaspaceSize=157286K"]`
it("does not execute command substitution in JAVA_OPTS (#1301, no eval)", func() {
deployment, logs, err := platform.Deploy.
WithEnv(map[string]string{
"BP_JAVA_VERSION": "17",
"JBP_CONFIG_JAVA_OPTS": `{from_environment: true, ` + memoryOpts + `}`,
// $(hostname) would run under the old eval start command. It must
// instead arrive at the JVM verbatim.
"JAVA_OPTS": `-DuserProperty=$(hostname)`,
}).
Execute(name, filepath.Join(fixtures, "containers", "spring_boot_staged"))
Expect(err).NotTo(HaveOccurred(), logs.String)
Eventually(deployment).Should(matchers.Serve(
ContainSubstring("userProperty=$(hostname)"), // literal, not the executed hostname
).WithEndpoint("/jvm-args"))
})
// NOTE: glob/cron preservation and shell-metacharacter/ampersand/backslash
// fidelity are covered deterministically at the unit level in
// frameworks/java_opts_writer_test.go, which runs the real assembly script
// and the real javaexec tokenizer. They are intentionally not duplicated as
// docker integration tests (the switchblade docker harness mangles some
// metacharacters when passing env vars, which is a harness artifact, not
// buildpack behaviour). The command-substitution case above stays here
// because non-execution is the security property worth proving end-to-end.
it("applies framework opts without any configured JAVA_OPTS", func() {
deployment, logs, err := platform.Deploy.
WithEnv(map[string]string{
"BP_JAVA_VERSION": "17",
}).
Execute(name, filepath.Join(fixtures, "containers", "spring_boot_staged"))
Expect(err).NotTo(HaveOccurred(), logs.String)
// Verify Container Security Provider is always installed
Expect(logs.String()).To(ContainSubstring("Container Security Provider"))
// Verify application starts successfully
Eventually(deployment).Should(matchers.Serve(ContainSubstring("Hello from Spring Boot")))
})
it("verifies multiple frameworks (4) append JAVA_OPTS without overwriting each other", func() {
deployment, logs, err := platform.Deploy.
WithEnv(map[string]string{
"BP_JAVA_VERSION": "17",
// Reduce memory settings to fit within 1G limit (v4 calculator)
"JBP_CONFIG_JAVA_OPTS": `{from_environment: false, java_opts: ["-Xmx384M", "-XX:ReservedCodeCacheSize=120M", "-Xss512k", "-DcustomProp=testValue"]}`,
"JBP_CONFIG_DEBUG": `{enabled: true}`,
}).
Execute(name, filepath.Join(fixtures, "containers", "spring_boot_multi_framework"))
Expect(err).NotTo(HaveOccurred(), logs.String)
// Verify FOUR frameworks are detected:
// 1. Java Opts - user configured JAVA_OPTS
// 2. Container Security Provider - always enabled (provides mTLS support)
// 3. Debug - JDWP debug agent
// 4. JRebel - auto-detected via rebel-remote.xml in fixture
Expect(logs.String()).To(ContainSubstring("Java Opts"))
Expect(logs.String()).To(ContainSubstring("Container Security Provider"))
Expect(logs.String()).To(ContainSubstring("Remote Debug"))
Expect(logs.String()).To(ContainSubstring("JRebel"))
// Verify ALL opts from ALL frameworks are present at runtime (none were overwritten)
Eventually(deployment).Should(matchers.Serve(And(
// Framework 1: User-configured opts from JBP_CONFIG_JAVA_OPTS
ContainSubstring("-Xmx384M"),
ContainSubstring("customProp=testValue"),
// Framework 2: Container Security Provider opts
ContainSubstring("-Djava.security.properties="),
// Framework 3: Debug opts (JDWP agent)
ContainSubstring("-agentlib:jdwp="),
// Framework 4: JRebel opts
ContainSubstring("-agentpath:"),
ContainSubstring("jrebel"),
// JVMKill agent (from JRE, not a framework)
ContainSubstring("jvmkill"),
// Verify no staging paths anywhere
Not(ContainSubstring("/tmp/contents")),
)).WithEndpoint("/jvm-args"))
})
it("verifies from_environment=true preserves user JAVA_OPTS with 4 frameworks", func() {
deployment, logs, err := platform.Deploy.
WithEnv(map[string]string{
"BP_JAVA_VERSION": "17",
// Reduce memory settings to fit within 1G limit (v4 calculator)
"JBP_CONFIG_JAVA_OPTS": `{from_environment: true, java_opts: ["-XX:ReservedCodeCacheSize=120M", "-Xss512k", "-DconfigProp=fromBuildpack"]}`,
"JAVA_OPTS": "-DuserProp=fromEnvironment -Xmx256M",
"JBP_CONFIG_DEBUG": `{enabled: true}`,
}).
Execute(name, filepath.Join(fixtures, "containers", "spring_boot_multi_framework"))
Expect(err).NotTo(HaveOccurred(), logs.String)
// Verify FOUR frameworks are detected:
// 1. Java Opts - user configured JAVA_OPTS
// 2. Container Security Provider - always enabled
// 3. Debug - JDWP debug agent
// 4. JRebel - auto-detected via rebel-remote.xml in fixture
Expect(logs.String()).To(ContainSubstring("Java Opts"))
Expect(logs.String()).To(ContainSubstring("Container Security Provider"))
Expect(logs.String()).To(ContainSubstring("Remote Debug"))
Expect(logs.String()).To(ContainSubstring("JRebel"))
// Verify ALL opts are present: user's JAVA_OPTS + configured opts + ALL framework opts
Eventually(deployment).Should(matchers.Serve(And(
// User's original JAVA_OPTS (should be preserved with from_environment: true)
ContainSubstring("userProp=fromEnvironment"),
ContainSubstring("-Xmx256M"),
// Configured opts from buildpack (Framework 1)
ContainSubstring("configProp=fromBuildpack"),
// Framework 2: Container Security Provider opts
// Framework 3: Debug opts (JDWP agent)
ContainSubstring("-agentlib:jdwp="),
// Framework 4: JRebel opts
ContainSubstring("-agentpath:"),
ContainSubstring("jrebel"),
// JVMKill agent (from JRE, not a framework)
ContainSubstring("jvmkill"),
// No staging paths anywhere
Not(ContainSubstring("/tmp/contents")),
)).WithEndpoint("/jvm-args"))
})
})
}
}