Split sample app

- spring-shell-sample-commands and spring-shell-sample-e2e
- Needed changes in e2e tests and workflow
- Fixes #754
This commit is contained in:
Janne Valkealahti
2023-06-15 10:31:05 +01:00
parent 71ed64670f
commit c27b85fb0e
58 changed files with 257 additions and 31 deletions

View File

@@ -42,9 +42,12 @@ jobs:
name: spring-shell-samples-${{ matrix.nickname }}
retention-days: 1
path: |
spring-shell-samples/build/libs/*.jar
spring-shell-samples/build/native/nativeCompile/spring-shell-samples
spring-shell-samples/build/native/nativeCompile/spring-shell-samples.exe
spring-shell-samples/spring-shell-sample-commands/build/libs/*.jar
spring-shell-samples/spring-shell-sample-commands/build/native/nativeCompile/spring-shell-sample-commands
spring-shell-samples/spring-shell-sample-commands/build/native/nativeCompile/spring-shell-sample-commands.exe
spring-shell-samples/spring-shell-sample-e2e/build/libs/*.jar
spring-shell-samples/spring-shell-sample-e2e/build/native/nativeCompile/spring-shell-sample-e2e
spring-shell-samples/spring-shell-sample-e2e/build/native/nativeCompile/spring-shell-sample-e2e.exe
- name: Upload Build Logs
if: ${{ failure() }}
uses: actions/upload-artifact@v3

37
.vscode/launch.json vendored
View File

@@ -3,41 +3,48 @@
"configurations": [
{
"type": "java",
"name": "Sample interactive",
"name": "commands interactive",
"request": "launch",
"mainClass": "org.springframework.shell.samples.SpringShellSample",
"projectName": "spring-shell-samples"
"projectName": "spring-shell-sample-commands"
},
{
"type": "java",
"name": "Sample help",
"name": "commands help",
"request": "launch",
"mainClass": "org.springframework.shell.samples.SpringShellSample",
"projectName": "spring-shell-samples",
"projectName": "spring-shell-sample-commands",
"args": "help"
},
{
"type": "java",
"name": "Sample fail noarg",
"name": "commands fail noarg",
"request": "launch",
"mainClass": "org.springframework.shell.samples.SpringShellSample",
"projectName": "spring-shell-samples",
"projectName": "spring-shell-sample-commands",
"args": "fail"
},
{
"type": "java",
"name": "Sample fail arg",
"name": "commands fail arg",
"request": "launch",
"mainClass": "org.springframework.shell.samples.SpringShellSample",
"projectName": "spring-shell-samples",
"projectName": "spring-shell-sample-commands",
"args": "fail --elementType TYPE"
},
{
"type": "java",
"name": "e2e interactive",
"request": "launch",
"mainClass": "org.springframework.shell.samples.SpringShellSample",
"projectName": "spring-shell-sample-e2e"
},
{
"type": "java",
"name": "e2e reg error-handling",
"request": "launch",
"mainClass": "org.springframework.shell.samples.SpringShellSample",
"projectName": "spring-shell-samples",
"projectName": "spring-shell-sample-e2e",
"args": "e2e reg error-handling"
},
{
@@ -45,7 +52,7 @@
"name": "e2e reg error-handling arg1 throw1",
"request": "launch",
"mainClass": "org.springframework.shell.samples.SpringShellSample",
"projectName": "spring-shell-samples",
"projectName": "spring-shell-sample-e2e",
"args": "e2e reg error-handling --arg1 throw1"
},
{
@@ -53,7 +60,7 @@
"name": "e2e reg error-handling arg1 throw2",
"request": "launch",
"mainClass": "org.springframework.shell.samples.SpringShellSample",
"projectName": "spring-shell-samples",
"projectName": "spring-shell-sample-e2e",
"args": "e2e reg error-handling --arg1 throw2"
},
{
@@ -61,7 +68,7 @@
"name": "e2e reg error-handling arg1 throw3",
"request": "launch",
"mainClass": "org.springframework.shell.samples.SpringShellSample",
"projectName": "spring-shell-samples",
"projectName": "spring-shell-sample-e2e",
"args": "e2e reg error-handling --arg1 throw3"
},
{
@@ -69,7 +76,7 @@
"name": "e2e exit-code noarg",
"request": "launch",
"mainClass": "org.springframework.shell.samples.SpringShellSample",
"projectName": "spring-shell-samples",
"projectName": "spring-shell-sample-e2e",
"args": "e2e reg exit-code"
},
{
@@ -77,7 +84,7 @@
"name": "e2e exit-code arg hi",
"request": "launch",
"mainClass": "org.springframework.shell.samples.SpringShellSample",
"projectName": "spring-shell-samples",
"projectName": "spring-shell-sample-e2e",
"args": "e2e reg exit-code --arg1 hi"
},
{
@@ -85,7 +92,7 @@
"name": "e2e exit-code arg fun",
"request": "launch",
"mainClass": "org.springframework.shell.samples.SpringShellSample",
"projectName": "spring-shell-samples",
"projectName": "spring-shell-sample-e2e",
"args": "e2e reg exit-code --arg1 fun"
}
]

View File

@@ -31,7 +31,7 @@ Generic workflow to run `spring-shell-e2e-tests` is:
[source, bash]
----
spring-shell
$ ./gradlew build nativeCompile -PspringShellSampleE2E=true -x test
$ ./gradlew :spring-shell-samples:spring-shell-sample-e2e:build :spring-shell-samples:spring-shell-sample-e2e:nativeCompile -PspringShellSampleE2E=true -x test
spring-shell/e2e/spring-shell-e2e
$ npm install

View File

@@ -4,18 +4,28 @@ import * as path from 'path';
export const tempDir = path.join(__dirname, 'spring-shell', 'temp');
export const isWindows = os.platform() === 'win32';
export const cliPathRelative = isWindows
? '..\\..\\spring-shell-samples\\build\\native\\nativeCompile\\spring-shell-samples.exe'
: '../../spring-shell-samples/build/native/nativeCompile/spring-shell-samples';
? '..\\..\\spring-shell-samples\\spring-shell-sample-e2e\\build\\native\\nativeCompile\\spring-shell-sample-e2e.exe'
: '../../spring-shell-samples/spring-shell-sample-e2e/build/native/nativeCompile/spring-shell-sample-e2e';
export const commandsCliPathRelative = isWindows
? '..\\..\\spring-shell-samples\\spring-shell-sample-commands\\build\\native\\nativeCompile\\spring-shell-sample-commands.exe'
: '../../spring-shell-samples/spring-shell-sample-commands/build/native/nativeCompile/spring-shell-sample-commands';
export const jarPathRelative = isWindows
? '..\\..\\spring-shell-samples\\build\\libs\\spring-shell-samples.jar'
: '../../spring-shell-samples/build/libs/spring-shell-samples.jar';
? '..\\..\\spring-shell-samples\\spring-shell-sample-e2e\\build\\libs\\spring-shell-sample-e2e.jar'
: '../../spring-shell-samples/spring-shell-sample-e2e/build/libs/spring-shell-sample-e2e.jar';
export const commandsJarPathRelative = isWindows
? '..\\..\\spring-shell-samples\\spring-shell-sample-commands\\build\\libs\\spring-shell-sample-commands.jar'
: '../../spring-shell-samples/spring-shell-sample-commands/build/libs/spring-shell-sample-commands.jar';
export const cliPath = path.resolve(cliPathRelative);
export const commandsCliPath = path.resolve(commandsCliPathRelative);
export const jarPath = path.resolve(jarPathRelative);
export const commandsJarPath = path.resolve(commandsJarPathRelative);
export const nativeDesc = 'native';
export const jarDesc = 'jar';
export const jarCommand = isWindows ? 'java.exe' : 'java';
export const nativeCommand = cliPath;
export const commandsNativeCommand = commandsCliPath;
export const jarOptions = ['-jar', jarPath];
export const commandsJarOptions = ['-jar', commandsJarPath];
export const waitForExpectDefaultTimeout = 30000;
export const waitForExpectDefaultInterval = 2000;
export const testTimeout = 120000;

View File

@@ -5,8 +5,8 @@ import {
nativeDesc,
jarDesc,
jarCommand,
nativeCommand,
jarOptions,
commandsNativeCommand,
commandsJarOptions,
waitForExpectDefaultTimeout,
waitForExpectDefaultInterval,
testTimeout
@@ -71,7 +71,7 @@ describe('flow commands', () => {
describe(jarDesc, () => {
beforeAll(() => {
command = jarCommand;
options = jarOptions;
options = commandsJarOptions;
});
it(
@@ -92,7 +92,7 @@ describe('flow commands', () => {
*/
describe(nativeDesc, () => {
beforeAll(() => {
command = nativeCommand;
command = commandsNativeCommand;
options = [];
});

View File

@@ -45,7 +45,6 @@ include 'spring-shell-core-test-support'
include 'spring-shell-management'
include 'spring-shell-dependencies'
include 'spring-shell-docs'
include 'spring-shell-samples'
include 'spring-shell-standard'
include 'spring-shell-standard-commands'
include 'spring-shell-table'
@@ -56,12 +55,22 @@ file("${rootDir}/spring-shell-starters").eachDirMatch(~/spring-shell-starter.*/)
include "spring-shell-starters:${it.name}"
}
file("${rootDir}/spring-shell-samples").eachDirMatch(~/spring-shell-sample.*/) {
include "spring-shell-samples:${it.name}"
}
rootProject.children.each { project ->
if (project.name == 'spring-shell-starters') {
project.children.each { subproject ->
subproject.buildFileName = "${subproject.name}.gradle"
subproject.buildFileName = "${subproject.name}.gradle"
}
} else {
}
else if (project.name == 'spring-shell-samples') {
project.children.each { subproject ->
subproject.buildFileName = "${subproject.name}.gradle"
}
}
else {
project.buildFileName = "${project.name}.gradle"
}
}

View File

@@ -0,0 +1,39 @@
plugins {
id 'org.springframework.shell.sample'
id 'org.springframework.boot'
id 'org.graalvm.buildtools.native'
}
description = 'Spring Shell Sample Commands'
dependencies {
management platform(project(":spring-shell-management"))
implementation project(':spring-shell-starters:spring-shell-starter-jna')
testImplementation project(':spring-shell-starters:spring-shell-starter-test')
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.awaitility:awaitility'
}
springBoot {
buildInfo()
}
if (project.hasProperty('springShellSampleE2E') && springShellSampleE2E.toBoolean()) {
bootJar {
archiveName = "$baseName.$extension"
}
}
graalvmNative {
metadataRepository {
enabled = true
}
binaries {
main {
if (project.hasProperty('springShellSampleMusl') && springShellSampleMusl.toBoolean()) {
buildArgs.add('--static')
buildArgs.add('--libc=musl')
}
}
}
}

View File

@@ -4,7 +4,7 @@ plugins {
id 'org.graalvm.buildtools.native'
}
description = 'Spring Shell Samples'
description = 'Spring Shell Sample E2E'
dependencies {
management platform(project(":spring-shell-management"))

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2017-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.samples;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStyle;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.shell.command.annotation.CommandScan;
import org.springframework.shell.jline.PromptProvider;
/**
* Main entry point for the application.
*
* <p>Creates the application context and start the REPL.</p>
*
* @author Eric Bottard
* @author Janne Valkealahti
*/
@SpringBootApplication
@CommandScan
public class SpringShellSample {
public static void main(String[] args) throws Exception {
SpringApplication application = new SpringApplication(SpringShellSample.class);
application.setBannerMode(Mode.OFF);
application.run(args);
// TODO: follow up with boot why spring.main.banner-mode=off doesn't work
// SpringApplication.run(SpringShellSample.class, args);
}
@Bean
public PromptProvider myPromptProvider() {
return () -> new AttributedString("my-shell:>", AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW));
}
}

View File

@@ -0,0 +1,34 @@
spring:
main:
banner-mode: off
shell:
## pick global default option naming
# option:
# naming:
# case-type: noop
# case-type: camel
# case-type: snake
# case-type: kebab
# case-type: pascal
config:
env: SPRING_SHELL_SAMPLES_USER_HOME
location: "{userconfig}/spring-shell-samples"
history:
name: spring-shell-samples-history.log
command:
help:
grouping-mode: group
completion:
root-command: spring-shell-samples
## disable console logging
logging:
pattern:
console:
## log debug from a cli
# file:
# name: shell.log
# level:
# root: debug
# org:
# springframework:
# shell: debug

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2022-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.samples;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.assertj.core.api.Condition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.shell.test.ShellAssertions;
import org.springframework.shell.test.ShellTestClient;
import org.springframework.shell.test.ShellTestClient.BaseShellSession;
import org.springframework.shell.test.ShellTestClient.InteractiveShellSession;
import org.springframework.shell.test.ShellTestClient.NonInteractiveShellSession;
import org.springframework.shell.test.autoconfigure.ShellTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
@ShellTest(terminalWidth = 120)
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class AbstractSampleTests {
@Autowired
protected ShellTestClient client;
protected void assertScreenContainsText(BaseShellSession<?> session, String text) {
await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> {
ShellAssertions.assertThat(session.screen()).containsText(text);
});
}
protected void assertScreenNotContainsText(BaseShellSession<?> session, String textFound, String textNotFound) {
Condition<String> notCondition = new Condition<>(line -> line.contains(textNotFound),
String.format("Text '%s' not found", textNotFound));
await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> {
ShellAssertions.assertThat(session.screen()).containsText(textFound);
List<String> lines = session.screen().lines();
assertThat(lines).areNot(notCondition);
});
}
protected BaseShellSession<?> createSession(String command, boolean interactive) {
if (interactive) {
InteractiveShellSession session = client.interactive().run();
session.write(session.writeSequence().command(command).build());
return session;
}
else {
String[] commands = command.split(" ");
NonInteractiveShellSession session = client.nonInterative(commands).run();
return session;
}
}
}