Table done; fixes gh-111

This commit is contained in:
Marcin Grzejszczak
2018-11-19 18:38:43 +01:00
parent 4ab19000ab
commit 03666265f8
29 changed files with 333 additions and 147 deletions

View File

@@ -45,7 +45,7 @@ public class ReleaserApplication implements CommandLineRunner {
@Autowired SpringReleaser releaser;
@Autowired Parser parser;
@Override public void run(String... strings) throws Exception {
@Override public void run(String... strings) {
Options options = this.parser.parse(strings);
try {
this.releaser.release(options);

View File

@@ -37,11 +37,11 @@ class Task {
this.taskType = taskType;
}
void execute(Args args) {
TaskAndException execute(Args args) {
if (args.taskType != this.taskType) {
log.info("Skipping [{}] since task type is [{}] and should be [{}]]",
this.name, this.taskType, args.taskType);
return;
return TaskAndException.skipped(this);
}
try {
boolean interactive = args.interactive;
@@ -49,18 +49,28 @@ class Task {
if (interactive) {
boolean skipStep = stepSkipper.skipStep();
if (!skipStep) {
this.consumer.accept(args);
return runTask(args);
}
return TaskAndException.skipped(this);
} else {
this.consumer.accept(args);
return runTask(args);
}
} catch (Exception e) {
log.error("\n\n\nBUILD FAILED!!!\n\nException occurred for task <" +
log.error("\n\n\nBUILD FAILED!!!\n\nException occurred for project <" +
(args.project != null ? args.project.getName() : "") + "> task <" +
this.name + "> \n\nwith description <" + this.description + ">\n\n", e);
throw e;
if (this.taskType == TaskType.RELEASE) {
throw e;
}
return TaskAndException.failure(this, e);
}
}
private TaskAndException runTask(Args args) {
this.consumer.accept(args);
return TaskAndException.success(this);
}
private void printLog(boolean interactive) {
log.info("\n\n\n=== {} ===\n\n{} {}\n\n", this.header, this.description, interactive ? MSG : "");
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2013-2018 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
*
* http://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.cloud.release.internal.spring;
/**
* @author Marcin Grzejszczak
*/
class TaskAndException {
final Task task;
final TaskState taskState;
final Exception exception;
private TaskAndException(Task task, TaskState taskState) {
this.task = task;
this.taskState = taskState;
this.exception = null;
}
private TaskAndException(Task task, TaskState taskState, Exception exception) {
this.task = task;
this.taskState = taskState;
this.exception = exception;
}
static TaskAndException skipped(Task task) {
return new TaskAndException(task, TaskState.SKIPPED);
}
static TaskAndException success(Task task) {
return new TaskAndException(task, TaskState.SUCCESS);
}
static TaskAndException failure(Task task, Exception exception) {
return new TaskAndException(task, TaskState.FAILURE, exception);
}
enum TaskState {
SKIPPED, SUCCESS, FAILURE
}
}

View File

@@ -1,11 +1,18 @@
package org.springframework.cloud.release.internal.spring;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.jakewharton.fliptables.FlipTableConverters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
* All tasks that can be executed by the releaser
*
@@ -135,23 +142,22 @@ class Tasks {
static Task RELEASE = Tasks.task("release", "fr",
"FULL RELEASE",
"Perform a full release of this project without interruptions",
args -> DEFAULT_TASKS_PER_PROJECT.forEach(task -> task.execute(args)));
args -> new CompositeConsumer(DEFAULT_TASKS_PER_PROJECT).accept(args));
static Task POST_RELEASE = Tasks.task("postRelease", "pr",
"POST RELEASE TASKS",
"Perform post release tasks for this release without interruptions",
args -> DEFAULT_TASKS_PER_RELEASE.forEach(task -> task.execute(args)),
args -> new CompositeConsumer(DEFAULT_TASKS_PER_RELEASE).accept(args),
TaskType.POST_RELEASE);
static Task RELEASE_VERBOSE = Tasks.task("releaseVerbose", "r",
"FULL VERBOSE RELEASE",
"Perform a full release of this project in interactive mode (you'll be asked about skipping steps)",
args -> DEFAULT_TASKS_PER_PROJECT.forEach(task -> task.execute(args)));
args -> new CompositeConsumer(DEFAULT_TASKS_PER_PROJECT).accept(args));
static Task META_RELEASE = Tasks.task("metaRelease", "x",
"META RELEASE",
"Perform a meta release of projects",
args -> DEFAULT_TASKS_PER_PROJECT.forEach(task -> {
args.properties.getMetaRelease().setEnabled(true);
task.execute(args);
}));
args -> new CompositeConsumer(DEFAULT_TASKS_PER_PROJECT,
(args1 -> args.properties.getMetaRelease().setEnabled(true)))
.accept(args));
static final List<Task> COMPOSITE_TASKS = Stream.of(
RELEASE,
@@ -187,4 +193,83 @@ class Tasks {
enum TaskType {
RELEASE, POST_RELEASE
}
class CompositeConsumer implements Consumer<Args> {
private static final Logger log = LoggerFactory.getLogger(CompositeConsumer.class);
private final List<Task> tasks;
private final Consumer<Args> setup;
CompositeConsumer(List<Task> tasks) {
this.tasks = tasks;
this.setup = args -> {};
}
CompositeConsumer(List<Task> tasks, Consumer<Args> setup) {
this.tasks = tasks;
this.setup = setup;
}
@Override
public void accept(Args args) {
this.setup.accept(args);
List<Table> table = this.tasks.stream()
.map(task -> new Table(task.execute(args)))
.collect(Collectors.toList());
String string = "\n\n***** BUILD REPORT *****\n\n"
+ FlipTableConverters.fromIterable(table, Table.class)
+ "\n\n***** BUILD REPORT *****\n\n";
List<Table> brokenTasks = table.stream()
.filter(table1 -> StringUtils.hasText(table1.thrownException))
.collect(Collectors.toList());
if (!brokenTasks.isEmpty()) {
String brokenBuilds = "\n\n[BUILD UNSTABLE] One of the tasks is failing!\n\n" +
FlipTableConverters.fromIterable(brokenTasks, Table.class) + "\n\n";
log.info(string + brokenBuilds);
throw new IllegalStateException("[BUILD UNSTABLE] One of the tasks is failing! + \n\n\n" + brokenBuilds);
} else {
log.info(string);
}
}
}
class Table {
final String taskCaption;
final String taskDescription;
final String taskState;
final String thrownException;
Table(TaskAndException tae) {
this.taskCaption = tae.task.name;
this.taskDescription = tae.task.description;
this.taskState = tae.taskState.name().toLowerCase();
this.thrownException = tae.exception == null ? "" : Arrays
.stream(tae.exception.getStackTrace())
.map(s -> {
String[] strings = s.toString().split("\\.");
return strings[strings.length - 3] + "." + strings[strings.length - 2] + "." + strings[strings.length - 1];
})
.limit(15)
.collect(Collectors.joining("\n"));
}
public String getTaskCaption() {
return this.taskCaption;
}
public String getTaskDescription() {
return this.taskDescription;
}
public String getTaskState() {
return this.taskState;
}
public String getThrownException() {
return this.thrownException;
}
}

View File

@@ -40,7 +40,7 @@ public class TestDocumentationUpdater extends DocumentationUpdater {
}
@Override
String readIndexHtmlContents(File indexHtml) throws IOException {
String readIndexHtmlContents(File indexHtml) {
return response();
}

View File

@@ -526,7 +526,7 @@ public class AcceptanceTests {
return this.testPomReader.readPom(new File(dir, "pom.xml"));
}
private File emailTemplate() throws URISyntaxException {
private File emailTemplate() {
return new File("target/email.txt");
}
@@ -534,15 +534,15 @@ public class AcceptanceTests {
return new String(Files.readAllBytes(emailTemplate().toPath()));
}
private File blogTemplate() throws URISyntaxException {
private File blogTemplate() {
return new File("target/blog.md");
}
private File tweetTemplate() throws URISyntaxException {
private File tweetTemplate() {
return new File("target/tweet.txt");
}
private File releaseNotesTemplate() throws URISyntaxException {
private File releaseNotesTemplate() {
return new File("target/notes.md");
}
@@ -620,7 +620,7 @@ public class AcceptanceTests {
}
private Releaser defaultReleaser(String expectedVersion, String projectName,
ReleaserProperties properties) throws Exception {
ReleaserProperties properties) {
ProjectPomUpdater pomUpdater = new ProjectPomUpdater(properties);
ProjectBuilder projectBuilder = new ProjectBuilder(properties);
TestProjectGitHandler handler = new TestProjectGitHandler(properties,
@@ -644,7 +644,7 @@ public class AcceptanceTests {
return releaser;
}
private Releaser defaultMetaReleaser(ReleaserProperties properties) throws Exception {
private Releaser defaultMetaReleaser(ReleaserProperties properties) {
ProjectPomUpdater pomUpdater = new ProjectPomUpdater(properties);
ProjectBuilder projectBuilder = new ProjectBuilder(properties);
NonAssertingTestProjectGitHandler handler = new NonAssertingTestProjectGitHandler(properties);

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2013-2018 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
*
* http://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.cloud.release.internal.spring;
import java.util.Arrays;
import org.assertj.core.api.BDDAssertions;
import org.junit.Test;
/**
* @author Marcin Grzejszczak
*/
public class CompositeConsumerTests {
@Test
public void should_throw_exception_for_a_release_task() {
CompositeConsumer compositeConsumer = new CompositeConsumer(Arrays.asList(
new Task("foo", "foo", "foo", "foo",
(args -> {})),
new Task("bar", "bar", "bar", "bar",
(args -> { throw new MyException(); }))
));
BDDAssertions.thenThrownBy(() ->
compositeConsumer.accept(new Args(TaskType.RELEASE)))
.isInstanceOf(MyException.class);
}
@Test
public void should_throw_exception_for_a_post_release_task_after_creating_a_report() {
CompositeConsumer compositeConsumer = new CompositeConsumer(Arrays.asList(
new Task("foo", "foo", "foo", "foo",
(args -> {}), TaskType.POST_RELEASE),
new Task("bar", "bar", "bar", "bar",
(args -> { throw new MyException(); }), TaskType.POST_RELEASE)
));
BDDAssertions.thenThrownBy(() ->
compositeConsumer.accept(new Args(TaskType.POST_RELEASE)))
.isInstanceOf(IllegalStateException.class);
}
}
class MyException extends RuntimeException {}

View File

@@ -50,7 +50,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_throw_exception_when_an_invalid_option_was_picked() throws Exception {
public void should_throw_exception_when_an_invalid_option_was_picked() {
Options options = nonInteractiveOpts().options();
thenThrownBy(() -> this.optionsProcessor.processOptions(options, args()))
@@ -58,7 +58,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_execute_only_tasks_after_the_provided_one_using_full_name() throws Exception {
public void should_execute_only_tasks_after_the_provided_one_using_full_name() {
Options options = nonInteractiveOpts().startFrom("second").options();
this.optionsProcessor.processOptions(options, args());
@@ -70,7 +70,7 @@ public class OptionsProcessorTests {
@Test
public void should_execute_only_tasks_after_the_provided_one_using_short_name() throws Exception {
public void should_execute_only_tasks_after_the_provided_one_using_short_name() {
Options options = nonInteractiveOpts().startFrom("2").options();
this.optionsProcessor.processOptions(options, args());
@@ -81,7 +81,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_execute_only_tasks_from_range_using_full_name() throws Exception {
public void should_execute_only_tasks_from_range_using_full_name() {
Options options = nonInteractiveOpts().range("second-third").options();
this.optionsProcessor.processOptions(options, args());
@@ -92,7 +92,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_execute_only_tasks_from_range_using_short_name() throws Exception {
public void should_execute_only_tasks_from_range_using_short_name() {
Options options = nonInteractiveOpts().range("2-3").options();
this.optionsProcessor.processOptions(options, args());
@@ -103,7 +103,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_execute_only_tasks_from_range_using_full_name_with_same_range() throws Exception {
public void should_execute_only_tasks_from_range_using_full_name_with_same_range() {
Options options = nonInteractiveOpts().range("second-second").options();
this.optionsProcessor.processOptions(options, args());
@@ -114,7 +114,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_execute_only_tasks_from_range_using_short_name_with_same_range() throws Exception {
public void should_execute_only_tasks_from_range_using_short_name_with_same_range() {
Options options = nonInteractiveOpts().range("2-2").options();
this.optionsProcessor.processOptions(options, args());
@@ -125,7 +125,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_execute_only_tasks_from_multi_using_full_name() throws Exception {
public void should_execute_only_tasks_from_multi_using_full_name() {
Options options = nonInteractiveOpts().taskNames(list("first", "third")).options();
this.optionsProcessor.processOptions(options, args());
@@ -136,7 +136,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_execute_only_tasks_from_multi_using_short_name() throws Exception {
public void should_execute_only_tasks_from_multi_using_short_name() {
Options options = nonInteractiveOpts().taskNames(list("1", "3")).options();
this.optionsProcessor.processOptions(options, args());
@@ -147,7 +147,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_execute_interactively_only_single_task() throws Exception {
public void should_execute_interactively_only_single_task() {
this.optionsProcessor = new OptionsProcessor(this.releaser, new ReleaserProperties(), this.tasks) {
@Override String chosenOption() {
return "0";
@@ -163,7 +163,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_execute_interactively_range_of_tasks() throws Exception {
public void should_execute_interactively_range_of_tasks() {
this.optionsProcessor = new OptionsProcessor(this.releaser, new ReleaserProperties(), this.tasks) {
@Override String chosenOption() {
return "0-1";
@@ -179,7 +179,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_execute_interactively_start_from() throws Exception {
public void should_execute_interactively_start_from() {
this.optionsProcessor = new OptionsProcessor(this.releaser, new ReleaserProperties(), this.tasks) {
@Override String chosenOption() {
return "1-";
@@ -195,7 +195,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_execute_interactively_multi() throws Exception {
public void should_execute_interactively_multi() {
this.optionsProcessor = new OptionsProcessor(this.releaser, new ReleaserProperties(), this.tasks) {
@Override String chosenOption() {
return "0,2";
@@ -211,7 +211,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_execute_full_release() throws Exception {
public void should_execute_full_release() {
this.optionsProcessor = new OptionsProcessor(this.releaser, new ReleaserProperties(), this.tasks) {
@Override Task releaseTask() {
return OptionsProcessorTests.this.firstTask;
@@ -231,7 +231,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_execute_full_verbose_release() throws Exception {
public void should_execute_full_verbose_release() {
this.optionsProcessor = new OptionsProcessor(this.releaser, new ReleaserProperties(), this.tasks) {
@Override Task releaseVerboseTask() {
return OptionsProcessorTests.this.firstTask;
@@ -251,7 +251,7 @@ public class OptionsProcessorTests {
}
@Test
public void should_remove_single_quotes() throws Exception {
public void should_remove_single_quotes() {
Options options = interactiveOpts().fullRelease(true)
.range("'1-2'")
.startFrom("'c'")

View File

@@ -43,7 +43,7 @@ public class TaskTests {
then(someBool.get()).isTrue();
then(this.capture.toString())
.contains("BUILD FAILED!!!")
.contains("Exception occurred for task <foo>")
.contains("Exception occurred for project <> task <foo>")
.contains("with description <descr>");
}
}