Skip to content

Commit 97b099c

Browse files
committed
Formatters can HTTP PUT to an URL
1 parent e80a5dc commit 97b099c

File tree

20 files changed

+368
-97
lines changed

20 files changed

+368
-97
lines changed

History.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## [Git master](https://github.com/cucumber/cucumber-jvm/compare/v1.1.1...master)
22

3+
* [Core] Formatters and `--dotcucumber` can now write to a file or an URL (via HTTP PUT). This allows easier distribution of reports. (Aslak Hellesøy)
34
* [JUnit] Added `@Cucumber.Options.dotcucumber`, allowing metadata to be written from JUnit. Useful for code completion. ([#418](https://github.com/cucumber/cucumber-jvm/issues/418 Aslak Hellesøy)
45
* [Core] Embedded data fails to display in HTML reports due to invalid string passed from HTMLFormatter ([#412](https://github.com/cucumber/cucumber-jvm/issues/412) Aslak Hellesøy)
56
* [Scala] Downgrade to scala 2.9.2 - we'll only use stable versions from now on. (Aslak Hellesøy)

core/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,18 @@
6363
<version>2.1</version>
6464
<scope>test</scope>
6565
</dependency>
66+
<dependency>
67+
<groupId>org.webbitserver</groupId>
68+
<artifactId>webbit</artifactId>
69+
<version>0.4.14</version>
70+
<scope>test</scope>
71+
</dependency>
72+
<dependency>
73+
<groupId>org.webbitserver</groupId>
74+
<artifactId>webbit-rest</artifactId>
75+
<version>0.2.0</version>
76+
<scope>test</scope>
77+
</dependency>
6678
</dependencies>
6779

6880
<build>

core/src/main/java/cucumber/runtime/Glue.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import java.io.File;
77
import java.io.IOException;
8+
import java.net.URL;
89
import java.util.List;
910

1011

@@ -24,5 +25,5 @@ public interface Glue {
2425

2526
StepDefinitionMatch stepDefinitionMatch(String uri, Step step, I18n i18n);
2627

27-
void writeStepdefsJson(List<String> featurePaths, File dotCucumber) throws IOException;
28+
void writeStepdefsJson(List<String> featurePaths, URL dotCucumber) throws IOException;
2829
}

core/src/main/java/cucumber/runtime/RuntimeGlue.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import cucumber.runtime.autocomplete.MetaStepdef;
44
import cucumber.runtime.autocomplete.StepdefGenerator;
55
import cucumber.runtime.io.FileResourceLoader;
6-
import cucumber.runtime.io.UTF8FileWriter;
6+
import cucumber.runtime.io.URLOutputStream;
7+
import cucumber.runtime.io.UTF8OutputStreamWriter;
78
import cucumber.runtime.model.CucumberFeature;
89
import cucumber.runtime.xstream.LocalizedXStreams;
910
import gherkin.I18n;
@@ -12,9 +13,9 @@
1213
import gherkin.formatter.Argument;
1314
import gherkin.formatter.model.Step;
1415

15-
import java.io.File;
1616
import java.io.IOException;
1717
import java.io.Writer;
18+
import java.net.URL;
1819
import java.util.ArrayList;
1920
import java.util.Collections;
2021
import java.util.List;
@@ -100,16 +101,14 @@ private List<StepDefinitionMatch> stepDefinitionMatches(String uri, Step step) {
100101
}
101102

102103
@Override
103-
public void writeStepdefsJson(List<String> featurePaths, File dotCucumber) throws IOException {
104+
public void writeStepdefsJson(List<String> featurePaths, URL dotCucumber) throws IOException {
104105
if (dotCucumber != null) {
105106
List<CucumberFeature> features = load(new FileResourceLoader(), featurePaths, NO_FILTERS);
106107
List<MetaStepdef> metaStepdefs = new StepdefGenerator().generate(stepDefinitionsByPattern.values(), features);
107108
Gson gson = new GsonBuilder().setPrettyPrinting().create();
108109
String json = gson.toJson(metaStepdefs);
109110

110-
File file = new File(dotCucumber, "stepdefs.json");
111-
Utils.ensureParentDirExists(file);
112-
Writer stepdefsJson = new UTF8FileWriter(file);
111+
Writer stepdefsJson = new UTF8OutputStreamWriter(new URLOutputStream(new URL(dotCucumber, "stepdefs.json")));
113112
stepdefsJson.append(json);
114113
stepdefsJson.close();
115114
}

core/src/main/java/cucumber/runtime/RuntimeOptions.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
import gherkin.formatter.Reporter;
99
import gherkin.util.FixJava;
1010

11-
import java.io.File;
1211
import java.lang.reflect.InvocationHandler;
1312
import java.lang.reflect.Method;
1413
import java.lang.reflect.Proxy;
14+
import java.net.MalformedURLException;
15+
import java.net.URL;
1516
import java.util.ArrayList;
1617
import java.util.List;
1718
import java.util.Properties;
@@ -32,7 +33,7 @@ public class RuntimeOptions {
3233
public final List<Formatter> formatters = new ArrayList<Formatter>();
3334
public final List<String> featurePaths = new ArrayList<String>();
3435
private final FormatterFactory formatterFactory = new FormatterFactory();
35-
public File dotCucumber;
36+
public URL dotCucumber;
3637
public boolean dryRun;
3738
public boolean strict = false;
3839
public boolean monochrome = false;
@@ -88,7 +89,8 @@ private void parse(List<String> args) {
8889
} else if (arg.equals("--format") || arg.equals("-f")) {
8990
formatters.add(formatterFactory.create(args.remove(0)));
9091
} else if (arg.equals("--dotcucumber")) {
91-
dotCucumber = new File(args.remove(0));
92+
String urlOrPath = args.remove(0);
93+
dotCucumber = Utils.toURL(urlOrPath);
9294
} else if (arg.equals("--no-dry-run") || arg.equals("--dry-run") || arg.equals("-d")) {
9395
dryRun = !arg.startsWith("--no-");
9496
} else if (arg.equals("--no-strict") || arg.equals("--strict") || arg.equals("-s")) {

core/src/main/java/cucumber/runtime/Utils.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import java.lang.reflect.Modifier;
88
import java.lang.reflect.ParameterizedType;
99
import java.lang.reflect.Type;
10+
import java.net.MalformedURLException;
11+
import java.net.URL;
1012
import java.util.ArrayList;
1113
import java.util.List;
1214
import java.util.Map;
@@ -78,11 +80,26 @@ private static Type typeArg(Type type, Class<?> wantedRawType, int index) {
7880
}
7981

8082
public static void ensureParentDirExists(File file) throws IOException {
81-
if(file.getParentFile() != null && !file.getParentFile().isDirectory()) {
83+
if (file.getParentFile() != null && !file.getParentFile().isDirectory()) {
8284
boolean ok = file.getParentFile().mkdirs();
83-
if(!ok) {
85+
if (!ok) {
8486
throw new IOException("Failed to create directory " + file.getParentFile().getAbsolutePath());
8587
}
8688
}
8789
}
90+
91+
public static URL toURL(String pathOrUrl) {
92+
try {
93+
if (!pathOrUrl.endsWith("/")) {
94+
pathOrUrl = pathOrUrl + "/";
95+
}
96+
if (pathOrUrl.matches("^(file|http|https):.*")) {
97+
return new URL(pathOrUrl);
98+
} else {
99+
return new URL("file:" + pathOrUrl);
100+
}
101+
} catch (MalformedURLException e) {
102+
throw new CucumberException("Bad URL:" + pathOrUrl, e);
103+
}
104+
}
88105
}

core/src/main/java/cucumber/runtime/formatter/FormatterFactory.java

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package cucumber.runtime.formatter;
22

33
import cucumber.runtime.CucumberException;
4-
import cucumber.runtime.Utils;
5-
import cucumber.runtime.io.UTF8FileWriter;
4+
import cucumber.runtime.io.URLOutputStream;
5+
import cucumber.runtime.io.UTF8OutputStreamWriter;
66
import gherkin.formatter.Formatter;
77
import gherkin.formatter.JSONFormatter;
88
import gherkin.formatter.JSONPrettyFormatter;
@@ -12,11 +12,14 @@
1212
import java.io.OutputStreamWriter;
1313
import java.lang.reflect.Constructor;
1414
import java.lang.reflect.InvocationTargetException;
15+
import java.net.URL;
1516
import java.util.HashMap;
1617
import java.util.Map;
1718
import java.util.regex.Matcher;
1819
import java.util.regex.Pattern;
1920

21+
import static cucumber.runtime.Utils.toURL;
22+
2023
/**
2124
* This class creates {@link Formatter} instances (that may also implement {@link gherkin.formatter.Reporter} from
2225
* a String.
@@ -27,7 +30,7 @@
2730
* The formatter class must have a constructor that is either empty or takes a single {@link Appendable} or a {@link File}.
2831
*/
2932
public class FormatterFactory {
30-
private final Class[] CTOR_ARGS = new Class[]{null, Appendable.class, File.class};
33+
private final Class[] CTOR_ARGS = new Class[]{null, Appendable.class, URL.class, File.class};
3134

3235
private static final Map<String, Class<? extends Formatter>> FORMATTER_CLASSES = new HashMap<String, Class<? extends Formatter>>() {{
3336
put("null", NullFormatter.class);
@@ -65,11 +68,11 @@ public Formatter create(String formatterString) {
6568
}
6669
}
6770

68-
private Formatter instantiate(String formatterString, Class<? extends Formatter> formatterClass, String path) throws IOException {
71+
private Formatter instantiate(String formatterString, Class<? extends Formatter> formatterClass, String pathOrUrl) throws IOException {
6972
for (Class ctorArgClass : CTOR_ARGS) {
7073
Constructor<? extends Formatter> constructor = findConstructor(formatterClass, ctorArgClass);
7174
if (constructor != null) {
72-
Object ctorArg = convertOrNull(path, ctorArgClass);
75+
Object ctorArg = convertOrNull(pathOrUrl, ctorArgClass);
7376
try {
7477
if (ctorArgClass == null) {
7578
return constructor.newInstance();
@@ -88,25 +91,26 @@ private Formatter instantiate(String formatterString, Class<? extends Formatter>
8891
}
8992
}
9093
}
91-
throw new CucumberException(String.format("%s must have a constructor that is either empty or takes a %s or %s", formatterClass, Appendable.class.getName(), File.class.getName()));
94+
throw new CucumberException(String.format("%s must have a constructor that is either empty or takes a %s or %s", formatterClass, Appendable.class.getName(), URL.class.getName()));
9295
}
9396

94-
private Object convertOrNull(String path, Class ctorArgClass) throws IOException {
97+
private Object convertOrNull(String pathOrUrl, Class ctorArgClass) throws IOException {
9598
if (ctorArgClass == null) {
9699
return null;
97100
}
101+
if (ctorArgClass.equals(URL.class)) {
102+
if (pathOrUrl != null) {
103+
return toURL(pathOrUrl);
104+
}
105+
}
98106
if (ctorArgClass.equals(File.class)) {
99-
if (path != null) {
100-
File file = new File(path);
101-
Utils.ensureParentDirExists(file);
102-
return file;
107+
if (pathOrUrl != null) {
108+
return new File(pathOrUrl);
103109
}
104110
}
105111
if (ctorArgClass.equals(Appendable.class)) {
106-
if (path != null) {
107-
File file = new File(path);
108-
Utils.ensureParentDirExists(file);
109-
return new UTF8FileWriter(file);
112+
if (pathOrUrl != null) {
113+
return new UTF8OutputStreamWriter(new URLOutputStream(toURL(pathOrUrl)));
110114
} else {
111115
return defaultOutOrFailIfAlreadyUsed();
112116
}
@@ -147,7 +151,7 @@ private Appendable defaultOutOrFailIfAlreadyUsed() {
147151
if (defaultOut != null) {
148152
return defaultOut;
149153
} else {
150-
throw new CucumberException("Only one formatter can use STDOUT. If you use more than one formatter you must specify output path with FORMAT:PATH");
154+
throw new CucumberException("Only one formatter can use STDOUT. If you use more than one formatter you must specify output path with FORMAT:PATH_OR_URL");
151155
}
152156
} finally {
153157
defaultOut = null;

core/src/main/java/cucumber/runtime/formatter/HTMLFormatter.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cucumber.runtime.formatter;
22

33
import cucumber.runtime.CucumberException;
4+
import cucumber.runtime.io.URLOutputStream;
45
import gherkin.deps.com.google.gson.Gson;
56
import gherkin.deps.com.google.gson.GsonBuilder;
67
import gherkin.formatter.Formatter;
@@ -17,13 +18,11 @@
1718
import gherkin.formatter.model.Step;
1819

1920
import java.io.File;
20-
import java.io.FileNotFoundException;
21-
import java.io.FileOutputStream;
2221
import java.io.IOException;
2322
import java.io.InputStream;
2423
import java.io.OutputStream;
2524
import java.io.OutputStreamWriter;
26-
import java.io.UnsupportedEncodingException;
25+
import java.net.URL;
2726
import java.util.HashMap;
2827
import java.util.List;
2928
import java.util.Map;
@@ -43,13 +42,13 @@ class HTMLFormatter implements Formatter, Reporter {
4342
}
4443
};
4544

46-
private final File htmlReportDir;
45+
private final URL htmlReportDir;
4746
private NiceAppendable jsOut;
4847

4948
private boolean firstFeature = true;
5049
private int embeddedIndex;
5150

52-
public HTMLFormatter(File htmlReportDir) {
51+
public HTMLFormatter(URL htmlReportDir) {
5352
this.htmlReportDir = htmlReportDir;
5453
}
5554

@@ -202,20 +201,18 @@ private NiceAppendable jsOut() {
202201
if (jsOut == null) {
203202
try {
204203
jsOut = new NiceAppendable(new OutputStreamWriter(reportFileOutputStream(JS_REPORT_FILENAME), "UTF-8"));
205-
} catch (UnsupportedEncodingException e) {
204+
} catch (IOException e) {
206205
throw new CucumberException(e);
207206
}
208207
}
209208
return jsOut;
210209
}
211210

212211
private OutputStream reportFileOutputStream(String fileName) {
213-
htmlReportDir.mkdirs();
214-
File file = new File(htmlReportDir, fileName);
215212
try {
216-
return new FileOutputStream(file);
217-
} catch (FileNotFoundException e) {
218-
throw new CucumberException("Error creating file: " + file.getAbsolutePath(), e);
213+
return new URLOutputStream(new URL(htmlReportDir, fileName));
214+
} catch (IOException e) {
215+
throw new CucumberException(e);
219216
}
220217
}
221218

core/src/main/java/cucumber/runtime/formatter/JUnitFormatter.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package cucumber.runtime.formatter;
22

33
import cucumber.runtime.CucumberException;
4+
import cucumber.runtime.io.URLOutputStream;
5+
import cucumber.runtime.io.UTF8OutputStreamWriter;
46
import gherkin.formatter.Formatter;
57
import gherkin.formatter.Reporter;
68
import gherkin.formatter.model.Background;
@@ -22,24 +24,26 @@
2224
import javax.xml.transform.TransformerFactory;
2325
import javax.xml.transform.dom.DOMSource;
2426
import javax.xml.transform.stream.StreamResult;
25-
import java.io.File;
27+
import java.io.IOException;
2628
import java.io.PrintWriter;
2729
import java.io.StringWriter;
30+
import java.io.Writer;
31+
import java.net.URL;
2832
import java.text.DecimalFormat;
2933
import java.text.NumberFormat;
3034
import java.util.ArrayList;
3135
import java.util.List;
3236
import java.util.Locale;
3337

3438
class JUnitFormatter implements Formatter, Reporter {
35-
private final File out;
39+
private final Writer out;
3640
private final Document doc;
3741
private final Element rootElement;
3842

3943
private TestCase testCase;
4044

41-
public JUnitFormatter(File out) {
42-
this.out = out;
45+
public JUnitFormatter(URL out) throws IOException {
46+
this.out = new UTF8OutputStreamWriter(new URLOutputStream(out));
4347
try {
4448
doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
4549
rootElement = doc.createElement("testsuite");

0 commit comments

Comments
 (0)