Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Java style (camelcase) snippet generator option #561

Closed
wants to merge 8 commits into from

2 participants

@meza

After this discussion: #302 (comment)

Support for java style function names in snippets.

Configurable with --snippets in the cucumber.options. (junit and command line)
Values: underscore, camelcase
Default: underscore

Currently only supported by the JavaBackend

...rc/test/java/cucumber/runtime/RuntimeOptionsTest.java
@@ -196,4 +196,20 @@ public void set_strict_on_strict_aware_formatters() throws Exception {
verify((StrictAware)strictAwareFormatter).setStrict(true);
}
+
+ @Test
+ public void default_snippet_type() {
@aslakhellesoy Owner

Can you give these tests an intent-revealing name please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
.../cucumber/runtime/java/CamelCaseSnippetGenerator.java
((1 lines not shown))
+package cucumber.runtime.java;
+
+import cucumber.api.DataTable;
+import cucumber.runtime.snippets.ArgumentPattern;
+import cucumber.runtime.snippets.Snippet;
+import cucumber.runtime.snippets.SnippetGenerator;
+import gherkin.I18n;
+import gherkin.formatter.model.Step;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CamelCaseSnippetGenerator extends SnippetGenerator {
@aslakhellesoy Owner

I'd prefer delegation instead of inheritance here.

SnippetGenerator should be passed a FunctionNameSanitizer instance instead. That also makes it easier to test various FunctionNameSanitizer implementations in isolation.

@meza
meza added a note

Agreed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@meza meza referenced this pull request in guardian/cucumber-jvm
Merged

Clarifying test intention #2

.../src/main/java/cucumber/runtime/java/JavaBackend.java
@@ -129,4 +130,18 @@ void addHook(Annotation annotation, Method method) {
glue.addAfterHook(new JavaHookDefinition(method, tagExpressions, ((After) annotation).order(), timeout, objectFactory));
}
}
+
+ @Override
+ public void setSnippetType(SnippetType type) {
+ switch (type) {
+ case CAMELCASE:
+ snippetGenerator = new CamelCaseSnippetGenerator(new JavaSnippet());
+ break;
+ default:
@aslakhellesoy Owner

Should be case UNDERSCORE.

default should throw CucumberException

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
core/src/main/java/cucumber/runtime/SnippetType.java
((7 lines not shown))
+ private String type;
+
+ SnippetType(String type) {
+ this.type = type;
+ }
+
+ public static SnippetType fromString(String type) {
+ if (type == null) {
+ throw new IllegalArgumentException();
+ }
+ for (SnippetType snippetType : SnippetType.values()) {
+ if (type.equalsIgnoreCase(snippetType.type)) {
+ return snippetType;
+ }
+ }
+ throw new IllegalArgumentException();
@aslakhellesoy Owner

throw CucumberException and give the user a clue what was wrong and what he needs to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
junit/src/main/java/cucumber/api/junit/Cucumber.java
@@ -141,5 +142,10 @@ private void addChildren(List<CucumberFeature> cucumberFeatures) throws Initiali
String[] name() default {};
String dotcucumber() default "";
+
+ /**
+ * @return what format should the snippets use. underscore, camelcase
+ */
+ String snippets() default "underscore";
@aslakhellesoy Owner

Can we make this method return the enum instead?

@meza
meza added a note

Sure! Didn't want to break the pattern, but am more than happy to!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@meza

I believe I've incorporated all your suggestions

@aslakhellesoy aslakhellesoy referenced this pull request in cucumber/cucumber
Closed

Generates unconventional methods #646

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 19, 2013
  1. @meza

    Added snippet support to core

    meza authored
  2. @meza
  3. @meza

    Style fix

    meza authored
Commits on Jul 22, 2013
  1. @meza

    Merge pull request #1 from guardian/javaSnippetGenerator

    meza authored
    Java snippet generator
  2. @meza
  3. @meza

    Merge pull request #2 from guardian/javaSnippetGenerator

    meza authored
    Clarifying test intention
  4. @meza

    Code review fixes

    meza authored
  5. @meza

    Merge pull request #3 from guardian/javaSnippetGenerator

    meza authored
    Code review fixes
This page is out of date. Refresh to see the latest.
Showing with 224 additions and 25 deletions.
  1. +1 −1  core/src/main/java/cucumber/runtime/Runtime.java
  2. +8 −0 core/src/main/java/cucumber/runtime/RuntimeOptions.java
  3. +28 −0 core/src/main/java/cucumber/runtime/SnippetType.java
  4. +7 −0 core/src/main/java/cucumber/runtime/SnippetTypeAwareBackend.java
  5. +13 −1 core/src/main/java/cucumber/runtime/UndefinedStepsTracker.java
  6. +5 −0 core/src/main/java/cucumber/runtime/snippets/FunctionNameSanitizer.java
  7. +12 −19 core/src/main/java/cucumber/runtime/snippets/SnippetGenerator.java
  8. +21 −0 core/src/main/java/cucumber/runtime/snippets/UnderscoreFunctionNameSanitizer.java
  9. +1 −0  core/src/main/resources/cucumber/runtime/USAGE.txt
  10. +16 −0 core/src/test/java/cucumber/runtime/RuntimeOptionsTest.java
  11. +15 −1 core/src/test/java/cucumber/runtime/UndefinedStepsTrackerTest.java
  12. +31 −0 java/src/main/java/cucumber/runtime/java/CamelCaseFunctionNameSanitizer.java
  13. +18 −2 java/src/main/java/cucumber/runtime/java/JavaBackend.java
  14. +20 −0 java/src/test/java/cucumber/runtime/java/CamelCaseFunctionNameSanitizerTest.java
  15. +7 −1 junit/src/main/java/cucumber/api/junit/Cucumber.java
  16. +8 −0 junit/src/main/java/cucumber/runtime/junit/RuntimeOptionsFactory.java
  17. +13 −0 junit/src/test/java/cucumber/runtime/junit/RuntimeOptionsFactoryTest.java
View
2  core/src/main/java/cucumber/runtime/Runtime.java
@@ -162,7 +162,7 @@ private boolean hasErrors() {
}
public List<String> getSnippets() {
- return undefinedStepsTracker.getSnippets(backends);
+ return undefinedStepsTracker.getSnippets(backends, runtimeOptions.getSnippetType());
}
public Glue getGlue() {
View
8 core/src/main/java/cucumber/runtime/RuntimeOptions.java
@@ -37,6 +37,7 @@
private boolean dryRun;
private boolean strict = false;
private boolean monochrome = false;
+ private SnippetType snippet = SnippetType.getDefault();
public RuntimeOptions(Properties properties, String... argv) {
/* IMPORTANT! Make sure USAGE.txt is always uptodate if this class changes */
@@ -97,6 +98,9 @@ private void parse(List<String> args) {
strict = !arg.startsWith("--no-");
} else if (arg.equals("--no-monochrome") || arg.equals("--monochrome") || arg.equals("-m")) {
monochrome = !arg.startsWith("--no-");
+ } else if (arg.equals("--snippets")) {
+ String nextArg = args.remove(0);
+ snippet = SnippetType.fromString(nextArg);
} else if (arg.equals("--name") || arg.equals("-n")) {
String nextArg = args.remove(0);
Pattern patternFilter = Pattern.compile(nextArg);
@@ -202,4 +206,8 @@ public URL getDotCucumber() {
public boolean isMonochrome() {
return monochrome;
}
+
+ public SnippetType getSnippetType() {
+ return snippet;
+ }
}
View
28 core/src/main/java/cucumber/runtime/SnippetType.java
@@ -0,0 +1,28 @@
+package cucumber.runtime;
+
+public enum SnippetType {
+ UNDERSCORE("underscore"),
+ CAMELCASE("camelcase");
+
+ private String type;
+
+ SnippetType(String type) {
+ this.type = type;
+ }
+
+ public static SnippetType fromString(String type) {
+ if (type == null) {
+ throw new CucumberException("Cannot pass null as SnippetType");
+ }
+ for (SnippetType snippetType : SnippetType.values()) {
+ if (type.equalsIgnoreCase(snippetType.type)) {
+ return snippetType;
+ }
+ }
+ throw new CucumberException(String.format("Unrecognized SnippetType %s", type));
+ }
+
+ public static SnippetType getDefault() {
+ return UNDERSCORE;
+ }
+}
View
7 core/src/main/java/cucumber/runtime/SnippetTypeAwareBackend.java
@@ -0,0 +1,7 @@
+package cucumber.runtime;
+
+public interface SnippetTypeAwareBackend extends Backend {
+
+ public void setSnippetType(SnippetType type);
+
+}
View
14 core/src/main/java/cucumber/runtime/UndefinedStepsTracker.java
@@ -22,11 +22,14 @@ public void reset() {
* @return a list of code snippets that the developer can use to implement undefined steps.
* This should be displayed after a run.
*/
- public List<String> getSnippets(Iterable<? extends Backend> backends) {
+ public List<String> getSnippets(Iterable<? extends Backend> backends, SnippetType type) {
// TODO: Convert "And" and "But" to the Given/When/Then keyword above in the Gherkin source.
List<String> snippets = new ArrayList<String>();
for (Step step : undefinedSteps) {
for (Backend backend : backends) {
+ if(backend instanceof SnippetTypeAwareBackend) {
+ ((SnippetTypeAwareBackend) backend).setSnippetType(type);
+ }
String snippet = backend.getSnippet(step);
if (snippet == null) {
throw new NullPointerException("null snippet");
@@ -39,6 +42,15 @@ public void reset() {
return snippets;
}
+ /**
+ * @param backends what backends we want snippets for
+ * @return a list of code snippets that the developer can use to implement undefined steps.
+ * This should be displayed after a run.
+ */
+ public List<String> getSnippets(Iterable<? extends Backend> backends) {
+ return getSnippets(backends, SnippetType.getDefault());
+ }
+
public void storeStepKeyword(Step step, I18n i18n) {
String keyword = step.getKeyword();
if (isGivenWhenThenKeyword(keyword, i18n)) {
View
5 core/src/main/java/cucumber/runtime/snippets/FunctionNameSanitizer.java
@@ -0,0 +1,5 @@
+package cucumber.runtime.snippets;
+
+public interface FunctionNameSanitizer {
+ public String sanitizeFunctionName(String functionName);
+}
View
31 core/src/main/java/cucumber/runtime/snippets/SnippetGenerator.java
@@ -1,6 +1,7 @@
package cucumber.runtime.snippets;
import cucumber.api.DataTable;
+import cucumber.runtime.SnippetType;
import gherkin.I18n;
import gherkin.formatter.model.Step;
@@ -10,7 +11,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public final class SnippetGenerator {
+public class SnippetGenerator {
private static final ArgumentPattern[] DEFAULT_ARGUMENT_PATTERNS = new ArgumentPattern[]{
new ArgumentPattern(Pattern.compile("\"([^\"]*)\""), String.class),
new ArgumentPattern(Pattern.compile("(\\d+)"), Integer.TYPE)
@@ -27,12 +28,19 @@
Pattern.compile("\\^"),};
private static final String REGEXP_HINT = "Express the Regexp above with the code you wish you had";
- private static final Character SUBST = '_';
+
private final Snippet snippet;
+ private FunctionNameSanitizer sanitizer;
+
public SnippetGenerator(Snippet snippet) {
+ this(snippet, new UnderscoreFunctionNameSanitizer());
+ }
+
+ public SnippetGenerator(Snippet snippet, FunctionNameSanitizer sanitizer) {
this.snippet = snippet;
+ this.sanitizer = sanitizer;
}
public String getSnippet(Step step) {
@@ -69,25 +77,10 @@ private String functionName(String name) {
for (ArgumentPattern argumentPattern : argumentPatterns()) {
functionName = argumentPattern.replaceMatchesWithSpace(functionName);
}
- functionName = sanitizeFunctionName(functionName);
+ functionName = sanitizer.sanitizeFunctionName(functionName);
return functionName;
}
- String sanitizeFunctionName(String functionName) {
- StringBuilder sanitized = new StringBuilder();
-
- String trimmedFunctionName = functionName.trim();
-
- sanitized.append(Character.isJavaIdentifierStart(trimmedFunctionName.charAt(0)) ? trimmedFunctionName.charAt(0) : SUBST);
- for (int i = 1; i < trimmedFunctionName.length(); i++) {
- if (Character.isJavaIdentifierPart(trimmedFunctionName.charAt(i))) {
- sanitized.append(trimmedFunctionName.charAt(i));
- } else if (sanitized.charAt(sanitized.length() - 1) != SUBST && i != trimmedFunctionName.length() - 1) {
- sanitized.append(SUBST);
- }
- }
- return sanitized.toString();
- }
private String withNamedGroups(String snippetPattern) {
Matcher m = GROUP_PATTERN.matcher(snippetPattern);
@@ -154,4 +147,4 @@ public static String untypedArguments(List<Class<?>> argumentTypes) {
}
return sb.toString();
}
-}
+}
View
21 core/src/main/java/cucumber/runtime/snippets/UnderscoreFunctionNameSanitizer.java
@@ -0,0 +1,21 @@
+package cucumber.runtime.snippets;
+
+public class UnderscoreFunctionNameSanitizer implements FunctionNameSanitizer {
+ private static final Character SUBST = '_';
+
+ public String sanitizeFunctionName(String functionName) {
+ StringBuilder sanitized = new StringBuilder();
+
+ String trimmedFunctionName = functionName.trim();
+
+ sanitized.append(Character.isJavaIdentifierStart(trimmedFunctionName.charAt(0)) ? trimmedFunctionName.charAt(0) : SUBST);
+ for (int i = 1; i < trimmedFunctionName.length(); i++) {
+ if (Character.isJavaIdentifierPart(trimmedFunctionName.charAt(i))) {
+ sanitized.append(trimmedFunctionName.charAt(i));
+ } else if (sanitized.charAt(sanitized.length() - 1) != SUBST && i != trimmedFunctionName.length() - 1) {
+ sanitized.append(SUBST);
+ }
+ }
+ return sanitized.toString();
+ }
+}
View
1  core/src/main/resources/cucumber/runtime/USAGE.txt
@@ -11,6 +11,7 @@ Options:
-d, --[no-]-dry-run Skip execution of glue code.
-m, --[no-]-monochrome Don't colour terminal output.
-s, --[no-]-strict Treat undefined and pending steps as errors.
+ --snippets Snippet type: underscore, camelcase
--dotcucumber PATH_OR_URL Where to write out runtime information. PATH_OR_URL can be a file system
path or a URL.
-v, --version Print version.
View
16 core/src/test/java/cucumber/runtime/RuntimeOptionsTest.java
@@ -196,4 +196,20 @@ public void set_strict_on_strict_aware_formatters() throws Exception {
verify((StrictAware)strictAwareFormatter).setStrict(true);
}
+
+ @Test
+ public void ensure_default_snippet_type_is_underscore() {
+ Properties properties = new Properties();
+ RuntimeOptions runtimeOptions = new RuntimeOptions(properties);
+ assertEquals(SnippetType.UNDERSCORE, runtimeOptions.getSnippetType());
+ }
+
+ @Test
+ public void set_snippet_type() {
+ Properties properties = new Properties();
+ properties.setProperty("cucumber.options", "--snippets camelcase");
+ RuntimeOptions runtimeOptions = new RuntimeOptions(properties);
+ assertEquals(SnippetType.CAMELCASE, runtimeOptions.getSnippetType());
+ }
+
}
View
16 core/src/test/java/cucumber/runtime/UndefinedStepsTrackerTest.java
@@ -5,13 +5,14 @@
import gherkin.I18n;
import gherkin.formatter.model.Step;
import org.junit.Test;
+import org.mockito.Mockito;
import java.util.List;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
public class UndefinedStepsTrackerTest {
@@ -76,6 +77,19 @@ public void snippets_are_generated_for_correct_locale() throws Exception {
assertEquals("[Если ^Б$]", tracker.getSnippets(asList(backend)).toString());
}
+ @Test
+ public void set_snippet_type_on_capable_backends() {
+ Backend backend = Mockito.mock(SnippetTypeAwareBackend.class);
+ Mockito.when(backend.getSnippet(Mockito.any(Step.class))).thenReturn("");
+
+ UndefinedStepsTracker undefinedStepsTracker = new UndefinedStepsTracker();
+ undefinedStepsTracker.addUndefinedStep(new Step(null, "Given ", "A", 1, null, null), ENGLISH);
+ undefinedStepsTracker.getSnippets(asList(backend));
+ Mockito.verify((SnippetTypeAwareBackend) backend, Mockito.atLeastOnce()).setSnippetType(SnippetType.getDefault());
+
+ }
+
+
private class TestBackend implements Backend {
@Override
public void loadGlue(Glue glue, List<String> gluePaths) {
View
31 java/src/main/java/cucumber/runtime/java/CamelCaseFunctionNameSanitizer.java
@@ -0,0 +1,31 @@
+package cucumber.runtime.java;
+
+import cucumber.runtime.snippets.FunctionNameSanitizer;
+
+public class CamelCaseFunctionNameSanitizer implements FunctionNameSanitizer {
+
+ public String sanitizeFunctionName(String functionName) {
+
+ StringBuilder sanitized = new StringBuilder();
+
+ String trimmedFunctionName = functionName.trim();
+
+ if (!Character.isJavaIdentifierStart(trimmedFunctionName.charAt(0))) {
+ sanitized.append("_");
+ }
+
+ String[] words = trimmedFunctionName.split(" ");
+
+ sanitized.append(words[0].toLowerCase());
+
+ for (int i = 1; i < words.length; i++) {
+ sanitized.append(capitalize(words[i].toLowerCase()));
+ }
+
+ return sanitized.toString();
+ }
+
+ private String capitalize(String line) {
+ return Character.toUpperCase(line.charAt(0)) + line.substring(1);
+ }
+}
View
20 java/src/main/java/cucumber/runtime/java/JavaBackend.java
@@ -5,6 +5,7 @@
import cucumber.runtime.*;
import cucumber.runtime.io.*;
import cucumber.runtime.snippets.SnippetGenerator;
+import cucumber.runtime.snippets.UnderscoreFunctionNameSanitizer;
import gherkin.formatter.model.Step;
import java.lang.annotation.Annotation;
@@ -12,8 +13,8 @@
import java.util.List;
import java.util.regex.Pattern;
-public class JavaBackend implements Backend {
- private final SnippetGenerator snippetGenerator = new SnippetGenerator(new JavaSnippet());
+public class JavaBackend implements SnippetTypeAwareBackend {
+ private SnippetGenerator snippetGenerator = new SnippetGenerator(new JavaSnippet(), new UnderscoreFunctionNameSanitizer());
private final ObjectFactory objectFactory;
private final Reflections reflections;
@@ -129,4 +130,19 @@ void addHook(Annotation annotation, Method method) {
glue.addAfterHook(new JavaHookDefinition(method, tagExpressions, ((After) annotation).order(), timeout, objectFactory));
}
}
+
+ @Override
+ public void setSnippetType(SnippetType type) {
+ switch (type) {
+ case CAMELCASE:
+ snippetGenerator = new SnippetGenerator(new JavaSnippet(), new CamelCaseFunctionNameSanitizer());
+ break;
+ case UNDERSCORE:
+ snippetGenerator = new SnippetGenerator(new JavaSnippet(), new UnderscoreFunctionNameSanitizer());
+ break;
+ default:
+ throw new CucumberException(String.format("Unsupported Snippet Type: %s", type.toString()));
+
+ }
+ }
}
View
20 java/src/test/java/cucumber/runtime/java/CamelCaseFunctionNameSanitizerTest.java
@@ -0,0 +1,20 @@
+package cucumber.runtime.java;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class CamelCaseFunctionNameSanitizerTest {
+
+ @Test
+ public void testSanitizeFunctionName() {
+
+ CamelCaseFunctionNameSanitizer generator = new CamelCaseFunctionNameSanitizer();
+
+ String functionName = "I am a function name";
+ String expected = "iAmAFunctionName";
+ String actual = generator.sanitizeFunctionName(functionName);
+
+ assertEquals(expected, actual);
+ }
+}
View
8 junit/src/main/java/cucumber/api/junit/Cucumber.java
@@ -2,6 +2,7 @@
import cucumber.runtime.Runtime;
import cucumber.runtime.RuntimeOptions;
+import cucumber.runtime.SnippetType;
import cucumber.runtime.io.MultiLoader;
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.junit.Assertions;
@@ -141,5 +142,10 @@ private void addChildren(List<CucumberFeature> cucumberFeatures) throws Initiali
String[] name() default {};
String dotcucumber() default "";
+
+ /**
+ * @return what format should the snippets use. underscore, camelcase
+ */
+ SnippetType snippets() default SnippetType.UNDERSCORE;
}
-}
+}
View
8 junit/src/main/java/cucumber/runtime/junit/RuntimeOptionsFactory.java
@@ -28,6 +28,7 @@ public RuntimeOptions create() {
addStrict(options, args);
addName(options, args);
addDotCucumber(options, args);
+ addSnippets(options, args);
RuntimeOptions runtimeOptions = new RuntimeOptions(System.getProperties(), args.toArray(new String[args.size()]));
@@ -54,6 +55,13 @@ private void addName(Cucumber.Options options, List<String> args) {
}
}
+ private void addSnippets(Cucumber.Options options, List<String> args) {
+ if (options != null) {
+ args.add("--snippets");
+ args.add(options.snippets().toString());
+ }
+ }
+
private Cucumber.Options getOptions(Class<?> clazz) {
return clazz.getAnnotation(Cucumber.Options.class);
}
View
13 junit/src/test/java/cucumber/runtime/junit/RuntimeOptionsFactoryTest.java
@@ -2,6 +2,7 @@
import cucumber.api.junit.Cucumber;
import cucumber.runtime.RuntimeOptions;
+import cucumber.runtime.SnippetType;
import org.junit.Test;
import java.net.MalformedURLException;
@@ -72,6 +73,13 @@ public void create_with_dotcucumber_url() throws MalformedURLException {
assertEquals(new URL("https://some.where/.cucumber/"), runtimeOptions.getDotCucumber());
}
+ @Test
+ public void create_with_snippets() {
+ RuntimeOptionsFactory runtimeOptionsFactory = new RuntimeOptionsFactory(Snippets.class);
+ RuntimeOptions runtimeOptions = runtimeOptionsFactory.create();
+ assertEquals(SnippetType.CAMELCASE, runtimeOptions.getSnippetType());
+ }
+
private String getRegexpPattern(Object pattern) {
return ((Pattern) pattern).pattern();
}
@@ -86,6 +94,11 @@ public void finds_path_for_class_in_toplevel_package() {
assertEquals("", packageName("TopLevelClass"));
}
+ @Cucumber.Options(snippets = SnippetType.CAMELCASE)
+ static class Snippets {
+ // empty
+ }
+
@Cucumber.Options(strict = true)
static class Strict {
// empty
Something went wrong with that request. Please try again.