diff --git a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/RegexpParser.java b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/RegexpParser.java new file mode 100644 index 000000000..74116664b --- /dev/null +++ b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/RegexpParser.java @@ -0,0 +1,36 @@ +package org.springframework.ide.vscode.commons.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RegexpParser implements ValueParser { + + final private Pattern pat; + final private String typeName; // used only for error message + final private String patternDescription; //Human readable description of the regexp pay + + /** + * Create a RegexpParser which succeeds if the input string matches the given regexp + * and fail otherwise. + * + * @param regexp + * @param typeName Name of the type (used in error message for failing parses) + * @param patternDescription Human readable description of the regexp pattern (included in the error message for failing parses) + */ + public RegexpParser(String regexp, String typeName, String patternDescription) { + super(); + this.pat = Pattern.compile(regexp); + this.typeName = typeName; + this.patternDescription = patternDescription; + } + + @Override + public Object parse(String str) { + Matcher matcher = pat.matcher(str); + if (matcher.matches()) { + return matcher; + } + throw new IllegalArgumentException("'"+str+"' is not a valid '"+typeName+"'. "+patternDescription); + } + +} diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java index 2b96d03d4..d36b8aa8c 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java @@ -52,6 +52,9 @@ public class PipelineYmlSchema implements YamlSchema { YType t_params = f.ymap(t_string, t_any); YType t_string_params = f.ymap(t_string, t_string); + YAtomicType t_duration = f.yatomic("Duration"); + t_duration.parseWith(ValueParsers.DURATION); + YAtomicType t_version = f.yatomic("Version"); t_version.addHints("latest", "every"); @@ -142,6 +145,7 @@ public class PipelineYmlSchema implements YamlSchema { prop(step, "ensure", step); prop(step, "attempts", t_strictly_pos_integer); prop(step, "tags", t_strings); + prop(step, "timeout", t_duration); YBeanType resource = f.ybean("Resource"); prop(resource, "name", t_ne_string); diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ValueParsers.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ValueParsers.java index 5f16013c1..7b1ea3743 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ValueParsers.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ValueParsers.java @@ -11,6 +11,7 @@ package org.springframework.ide.vscode.concourse; import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.RegexpParser; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.ValueParser; @@ -56,5 +57,16 @@ public class ValueParsers { } }; } + + public static ValueParser DURATION = new RegexpParser( + "^(([0-9]+(.[0-9]+)?)(ns|us|µs|ms|s|h|m))+$", + "Duration", + " A duration string is a sequence of decimal numbers, each with " + + "optional fraction and a unit suffix, such as '300ms', '1.5h' or" + + " '2h45m'. Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', " + + "'m', 'h'." + ); + + } diff --git a/vscode-extensions/vscode-concourse/src/main/resources/desc/Step/timeout.md b/vscode-extensions/vscode-concourse/src/main/resources/desc/Step/timeout.md new file mode 100644 index 000000000..7b39c49c5 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/desc/Step/timeout.md @@ -0,0 +1,17 @@ +Enforce a time limit on a step. + +Any step can have a hard time limit enforced by attaching timeout and the number of seconds to limit it to. + + timeout: duration + +The amount of time to limit the step's execution to, e.g. `30m` for 30 minutes. + +When exceeded, the step will be interrupted, with the same semantics as aborting the build (except the build will be `failed`, not `aborted`, to distinguish between human intervention and timeouts being enforced). + +The following will run the task, and cancel it if it takes longer than 1 hour and 30 minutes: + + plan: + - get: foo + - task: unit + file: foo/unit.yml + timeout: 1h30m \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/DurationParserTest.java b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/DurationParserTest.java new file mode 100644 index 000000000..da382d586 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/DurationParserTest.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.concourse; + +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.springframework.ide.vscode.commons.util.ExceptionUtil; +import org.springframework.ide.vscode.commons.util.ValueParser; + +import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.*; + +public class DurationParserTest { + + private ValueParser parser = ValueParsers.DURATION; + + @Test + public void goodExamples() { + parser.parse("1h40m"); + parser.parse("1.5h"); + parser.parse("23h59m59s99ms200µs100ns"); + } + + @Test + public void badExamples() { + does_not_parse("1h:40m"); + does_not_parse("15h 30m"); + does_not_parse("23hours"); + } + + private void does_not_parse(String string) { + try { + parser.parse(string); + fail("Should have failed parsing!"); + } catch (IllegalArgumentException e) { + assertContains("Duration", ExceptionUtil.getMessage(e)); + } + } + +} diff --git a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java index c7a74c2c1..33fd4f62b 100644 --- a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java +++ b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java @@ -252,6 +252,7 @@ public class PipelineYamlEditorTest { " config: some-config\n" + " tags: [a, b, c]\n"+ " attempts: 10\n" + + " timeout: 1h30m\n" + " ensure:\n" + " bogus: bad\n" + " on_failure:\n" + @@ -267,6 +268,7 @@ public class PipelineYamlEditorTest { editor.assertHoverContains("output_mapping", "A map from task output names to concrete names"); editor.assertHoverContains("config", "Use `config` to inline the task config"); editor.assertHoverContains("tags", "Any step can be directed at a pool of workers"); + editor.assertHoverContains("timeout", "amount of time to limit the step's execution"); } @Test @@ -297,14 +299,16 @@ public class PipelineYamlEditorTest { " plan:\n" + " - get: git\n" + " trigger: yohoho\n" + - " attempts: 0\n" + " attempts: 0\n" + + " timeout: 1h:30m\n" ); editor.assertProblems( "boohoo|boolean", "-1|must be positive", "git|resource does not exist", "yohoho|boolean", - "0|must be at least 1" + "0|must be at least 1", + "1h:30m|Duration" ); //check that correct values are indeed accepted @@ -508,6 +512,7 @@ public class PipelineYamlEditorTest { ); } + ////////////////////////////////////////////////////////////////////////////// private void assertContextualCompletions(String conText, String textBefore, String... textAfter) throws Exception {