From e9cd3fadfffeeda1fe9e5330d891079d9455421c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 11 Oct 2022 15:16:34 +0200 Subject: [PATCH] [Core] Delegate encoding and BOM handling to gherkin Previously before handing over a feature file to the Gherkin parser, Cucumber would remove any Byte Order Markers (BOM) and determine the encoding of the feature file based on the `#encoding: ` comment. With https://github.com/cucumber/common/pull/2018 this can now be handled by Gherkin. Removing it simplifies Cucumber a little. Unfortunately the `FeatureParser` doesn't take a `InputStream` as an argument. So we need to add a default interface to avoid breaking semver. --- CHANGELOG.md | 4 + .../cucumber/core/feature/EncodingParser.java | 73 --------- .../cucumber/core/feature/FeatureParser.java | 15 +- .../core/feature/EncodingParserTest.java | 44 ----- .../core/feature/UTF_8_BOM_Encoded.feature | 8 - .../core/feature/UTF_8_Encoded.feature | 8 - cucumber-gherkin-messages/pom.xml | 1 - .../GherkinMessagesFeatureParser.java | 26 ++- .../gherkin/messages/FeatureParserTest.java | 154 ++++++++++-------- cucumber-gherkin/pom.xml | 13 ++ .../cucumber/core/gherkin/FeatureParser.java | 18 ++ .../core/gherkin/FeatureParserTest.java | 50 ++++++ 12 files changed, 202 insertions(+), 212 deletions(-) delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/feature/EncodingParser.java delete mode 100644 cucumber-core/src/test/java/io/cucumber/core/feature/EncodingParserTest.java delete mode 100644 cucumber-core/src/test/resources/io/cucumber/core/feature/UTF_8_BOM_Encoded.feature delete mode 100644 cucumber-core/src/test/resources/io/cucumber/core/feature/UTF_8_Encoded.feature create mode 100644 cucumber-gherkin/src/test/java/io/cucumber/core/gherkin/FeatureParserTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 98a2cda1e6..f07da866fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- [Core] Update dependency io.cucumber:gherkin to v24.1 +- [Core] Delegate encoding and BOM handling to gherkin ([2624](https://github.com/cucumber/cucumber-jvm/issues/2624) M.P. Korstanje) + ## [7.8.1] - 2022-10-03 ### Fixed - [Core] Remove Jackson services from `META-INF/services` ([#2621](https://github.com/cucumber/cucumber-jvm/issues/2621) M.P. Korstanje) diff --git a/cucumber-core/src/main/java/io/cucumber/core/feature/EncodingParser.java b/cucumber-core/src/main/java/io/cucumber/core/feature/EncodingParser.java deleted file mode 100644 index 6c636f4feb..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/feature/EncodingParser.java +++ /dev/null @@ -1,73 +0,0 @@ -package io.cucumber.core.feature; - -import io.cucumber.core.exception.CucumberException; -import io.cucumber.core.resource.Resource; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Locale.ROOT; - -/** - * Parser for the {@code # encoding: } header. Will reload the file - * in the specified encoding if not UTF-8. - */ -final class EncodingParser { - - private static final Pattern COMMENT_OR_EMPTY_LINE_PATTERN = Pattern.compile("^\\s*#|^\\s*$"); - private static final Pattern ENCODING_PATTERN = Pattern.compile("^\\s*#\\s*encoding\\s*:\\s*([0-9a-zA-Z\\-]+)", - Pattern.CASE_INSENSITIVE); - private static final String DEFAULT_ENCODING = UTF_8.name(); - private static final String UTF_8_BOM = "\uFEFF"; - - String parse(Resource resource) { - String source = read(resource, DEFAULT_ENCODING); - // Remove UTF8 BOM encoded in first bytes - if (source.startsWith(UTF_8_BOM)) { - source = source.replaceFirst(UTF_8_BOM, ""); - } - String enc = encoding(source); - if (!enc.equals(DEFAULT_ENCODING)) { - source = read(resource, enc); - } - return source; - } - - private static String read(Resource resource, String encoding) { - char[] buffer = new char[2 * 1024]; - final StringBuilder out = new StringBuilder(); - try ( - InputStream is = resource.getInputStream(); - InputStreamReader in = new InputStreamReader(is, encoding); - BufferedReader reader = new BufferedReader(in);) { - int read; - while ((read = reader.read(buffer, 0, buffer.length)) > 0) { - out.append(buffer, 0, read); - } - } catch (IOException e) { - throw new CucumberException("Failed to read resource:" + resource.getUri(), e); - } - return out.toString(); - } - - private static String encoding(String source) { - String encoding = DEFAULT_ENCODING; - for (String line : source.split("\\n")) { - if (!COMMENT_OR_EMPTY_LINE_PATTERN.matcher(line).find()) { - break; - } - Matcher matcher = ENCODING_PATTERN.matcher(line); - if (matcher.find()) { - encoding = matcher.group(1); - break; - } - } - return encoding.toUpperCase(ROOT); - } - -} diff --git a/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureParser.java b/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureParser.java index 0c0e986dc9..07adeac5e1 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureParser.java +++ b/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureParser.java @@ -1,8 +1,11 @@ package io.cucumber.core.feature; import io.cucumber.core.gherkin.Feature; +import io.cucumber.core.gherkin.FeatureParserException; import io.cucumber.core.resource.Resource; +import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.Collections; @@ -19,8 +22,6 @@ public final class FeatureParser { - private final EncodingParser encodingParser = new EncodingParser(); - private final Supplier idGenerator; public FeatureParser(Supplier idGenerator) { @@ -31,8 +32,6 @@ public Optional parseResource(Resource resource) { requireNonNull(resource); URI uri = resource.getUri(); - String source = encodingParser.parse(resource); - ServiceLoader services = ServiceLoader .load(io.cucumber.core.gherkin.FeatureParser.class); Iterator iterator = services.iterator(); @@ -42,7 +41,13 @@ public Optional parseResource(Resource resource) { } Comparator version = comparing( io.cucumber.core.gherkin.FeatureParser::version); - return Collections.max(parser, version).parse(uri, source, idGenerator); + + try (InputStream source = resource.getInputStream()) { + return Collections.max(parser, version).parse(uri, source, idGenerator); + + } catch (IOException e) { + throw new FeatureParserException("Failed to parse resource at: " + uri, e); + } } } diff --git a/cucumber-core/src/test/java/io/cucumber/core/feature/EncodingParserTest.java b/cucumber-core/src/test/java/io/cucumber/core/feature/EncodingParserTest.java deleted file mode 100644 index 2a0e3cca4d..0000000000 --- a/cucumber-core/src/test/java/io/cucumber/core/feature/EncodingParserTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.cucumber.core.feature; - -import io.cucumber.core.resource.Resource; -import org.junit.jupiter.api.Test; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; - -import static org.junit.jupiter.api.Assertions.assertFalse; - -class EncodingParserTest { - - private final EncodingParser parser = new EncodingParser(); - - @Test - void test_utf8_bom_encode() throws RuntimeException, IOException { - Resource resource = resource("src/test/resources/io/cucumber/core/feature/UTF_8_BOM_Encoded.feature"); - assertFalse(parser.parse(resource).startsWith("\uFEFF"), "UTF-8 BOM encoding not removed."); - } - - @Test - void test_utf8_encode() throws RuntimeException, IOException { - Resource resource = resource("src/test/resources/io/cucumber/core/feature/UTF_8_Encoded.feature"); - assertFalse(parser.parse(resource).startsWith("\uFEFF"), "UTF-8 BOM encoding should not be present."); - } - - private Resource resource(String name) { - Resource resource = new Resource() { - @Override - public URI getUri() { - return null; - } - - @Override - public InputStream getInputStream() throws IOException { - - return new FileInputStream(name); - } - }; - return resource; - } -} diff --git a/cucumber-core/src/test/resources/io/cucumber/core/feature/UTF_8_BOM_Encoded.feature b/cucumber-core/src/test/resources/io/cucumber/core/feature/UTF_8_BOM_Encoded.feature deleted file mode 100644 index d96c383540..0000000000 --- a/cucumber-core/src/test/resources/io/cucumber/core/feature/UTF_8_BOM_Encoded.feature +++ /dev/null @@ -1,8 +0,0 @@ -#Sample comment - -Feature: UTF-8 BOM feature file - - Scenario: Pass UTF-8-BOM file - Given that I created UTF-8-BOM encoded feature-file - When I pass it to cucumber-jvm - Then it gets parsed normally diff --git a/cucumber-core/src/test/resources/io/cucumber/core/feature/UTF_8_Encoded.feature b/cucumber-core/src/test/resources/io/cucumber/core/feature/UTF_8_Encoded.feature deleted file mode 100644 index 0d655d5b97..0000000000 --- a/cucumber-core/src/test/resources/io/cucumber/core/feature/UTF_8_Encoded.feature +++ /dev/null @@ -1,8 +0,0 @@ -#Sample comment - -Feature: UTF-8 Feature file - - Scenario: Pass UTF file - Given that I created UTF-8 encoded feature-file - When I pass it to cucumber-jvm - Then it gets parsed normally diff --git a/cucumber-gherkin-messages/pom.xml b/cucumber-gherkin-messages/pom.xml index ac55342df4..93e3260052 100644 --- a/cucumber-gherkin-messages/pom.xml +++ b/cucumber-gherkin-messages/pom.xml @@ -49,7 +49,6 @@ org.junit.jupiter junit-jupiter - ${junit-jupiter.version} test diff --git a/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesFeatureParser.java b/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesFeatureParser.java index a272fdb82f..02b60aeb25 100644 --- a/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesFeatureParser.java +++ b/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesFeatureParser.java @@ -12,23 +12,36 @@ import io.cucumber.messages.types.ParseError; import io.cucumber.messages.types.Source; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.function.Supplier; -import static io.cucumber.messages.types.SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.toList; public final class GherkinMessagesFeatureParser implements FeatureParser { + @Deprecated @Override public Optional parse(URI path, String source, Supplier idGenerator) { + try (InputStream is = new ByteArrayInputStream(source.getBytes(UTF_8))) { + return parse(path, is, idGenerator); + } catch (IOException e) { + throw new FeatureParserException("Failed to parse resource at: " + path, e); + } + } + + @Override + public Optional parse(URI path, InputStream source, Supplier idGenerator) throws IOException { List envelopes = GherkinParser.builder() .idGenerator(() -> idGenerator.get().toString()) .build() - .parse(Envelope.of(new Source(path.toString(), source, TEXT_X_CUCUMBER_GHERKIN_PLAIN))) + .parse(path.toString(), source) .collect(toList()); List errors = envelopes.stream() @@ -70,10 +83,17 @@ public Optional parse(URI path, String source, Supplier idGenerat .map(pickle -> new GherkinMessagesPickle(pickle, path, dialect, cucumberQuery)) .collect(toList()); + Source sourceMessage = envelopes.stream() + .map(Envelope::getSource) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst() + .orElseThrow(() -> new IllegalStateException("source message was not emitted by parser")); + return new GherkinMessagesFeature( feature, path, - source, + sourceMessage.getData(), pickles, envelopes); }); diff --git a/cucumber-gherkin-messages/src/test/java/io/cucumber/core/gherkin/messages/FeatureParserTest.java b/cucumber-gherkin-messages/src/test/java/io/cucumber/core/gherkin/messages/FeatureParserTest.java index 229f75a43c..f7d634323d 100644 --- a/cucumber-gherkin-messages/src/test/java/io/cucumber/core/gherkin/messages/FeatureParserTest.java +++ b/cucumber-gherkin-messages/src/test/java/io/cucumber/core/gherkin/messages/FeatureParserTest.java @@ -10,7 +10,9 @@ import org.junit.jupiter.api.Test; import java.io.IOException; +import java.io.InputStream; import java.net.URI; +import java.nio.file.Files; import java.nio.file.Paths; import java.util.Iterator; import java.util.List; @@ -27,11 +29,11 @@ class FeatureParserTest { - private final GherkinMessagesFeatureParser parser = new GherkinMessagesFeatureParser(); + final GherkinMessagesFeatureParser parser = new GherkinMessagesFeatureParser(); + final URI uri = URI.create("classpath:com/example.feature"); @Test - void feature_file_without_pickles_is_parsed_produces_empty_feature() throws IOException { - URI uri = URI.create("classpath:com/example.feature"); + void can_parse_with_deprecated_method() throws IOException { String source = new String( readAllBytes(Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/no-pickles.feature"))); Optional feature = parser.parse(uri, source, UUID::randomUUID); @@ -39,95 +41,107 @@ void feature_file_without_pickles_is_parsed_produces_empty_feature() throws IOEx assertEquals(0, feature.get().getPickles().size()); } + @Test + void feature_file_without_pickles_is_parsed_produces_empty_feature() throws IOException { + try (InputStream source = Files.newInputStream( + Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/no-pickles.feature"))) { + Optional feature = parser.parse(uri, source, UUID::randomUUID); + assertTrue(feature.isPresent()); + assertEquals(0, feature.get().getPickles().size()); + } + } + @Test void empty_feature_file_is_parsed_but_produces_no_feature() throws IOException { - URI uri = URI.create("classpath:com/example.feature"); - String source = new String( - readAllBytes(Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/empty.feature"))); - Optional feature = parser.parse(uri, source, UUID::randomUUID); - assertFalse(feature.isPresent()); + try (InputStream source = Files.newInputStream( + Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/empty.feature"))) { + Optional feature = parser.parse(uri, source, UUID::randomUUID); + assertFalse(feature.isPresent()); + } } @Test void unnamed_elements_return_empty_strings_as_name() throws IOException { - URI uri = URI.create("classpath:com/example.feature"); - String source = new String( - readAllBytes(Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/unnamed.feature"))); - Feature feature = parser.parse(uri, source, UUID::randomUUID).get(); - assertEquals(Optional.empty(), feature.getName()); - Node.Rule rule = (Node.Rule) feature.elements().iterator().next(); - assertEquals(Optional.empty(), rule.getName()); - assertEquals(Optional.of("Rule"), rule.getKeyword()); - Iterator ruleElements = rule.elements().iterator(); - Node.Scenario scenario = (Node.Scenario) ruleElements.next(); - assertEquals(Optional.empty(), scenario.getName()); - assertEquals(Optional.of("Scenario"), scenario.getKeyword()); - Node.ScenarioOutline scenarioOutline = (Node.ScenarioOutline) ruleElements.next(); - assertEquals(Optional.empty(), scenarioOutline.getName()); - assertEquals(Optional.of("Scenario Outline"), scenarioOutline.getKeyword()); - Node.Examples examples = scenarioOutline.elements().iterator().next(); - assertEquals(Optional.empty(), examples.getName()); - assertEquals(Optional.of("Examples"), examples.getKeyword()); - Node.Example example = examples.elements().iterator().next(); - - // Example is the exception. - assertEquals(Optional.of("Example #1"), example.getName()); - assertEquals(Optional.empty(), example.getKeyword()); + try (InputStream source = Files.newInputStream( + Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/unnamed.feature"))) { + + Feature feature = parser.parse(uri, source, UUID::randomUUID).get(); + assertEquals(Optional.empty(), feature.getName()); + Node.Rule rule = (Node.Rule) feature.elements().iterator().next(); + assertEquals(Optional.empty(), rule.getName()); + assertEquals(Optional.of("Rule"), rule.getKeyword()); + Iterator ruleElements = rule.elements().iterator(); + Node.Scenario scenario = (Node.Scenario) ruleElements.next(); + assertEquals(Optional.empty(), scenario.getName()); + assertEquals(Optional.of("Scenario"), scenario.getKeyword()); + Node.ScenarioOutline scenarioOutline = (Node.ScenarioOutline) ruleElements.next(); + assertEquals(Optional.empty(), scenarioOutline.getName()); + assertEquals(Optional.of("Scenario Outline"), scenarioOutline.getKeyword()); + Node.Examples examples = scenarioOutline.elements().iterator().next(); + assertEquals(Optional.empty(), examples.getName()); + assertEquals(Optional.of("Examples"), examples.getKeyword()); + Node.Example example = examples.elements().iterator().next(); + + // Example is the exception. + assertEquals(Optional.of("Example #1"), example.getName()); + assertEquals(Optional.empty(), example.getKeyword()); + } } @Test void empty_table_is_parsed() throws IOException { - URI uri = URI.create("classpath:com/example.feature"); - String source = new String( - readAllBytes(Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/empty-table.feature"))); - Feature feature = parser.parse(uri, source, UUID::randomUUID).get(); - Pickle pickle = feature.getPickles().get(0); - Step step = pickle.getSteps().get(0); - DataTableArgument argument = (DataTableArgument) step.getArgument(); - assertEquals(5, argument.getLine()); + try (InputStream source = Files.newInputStream( + Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/empty-table.feature"))) { + Feature feature = parser.parse(uri, source, UUID::randomUUID).get(); + Pickle pickle = feature.getPickles().get(0); + Step step = pickle.getSteps().get(0); + DataTableArgument argument = (DataTableArgument) step.getArgument(); + assertEquals(5, argument.getLine()); + } } @Test void empty_doc_string_media_type_is_null() throws IOException { - URI uri = URI.create("classpath:com/example.feature"); - String source = new String( - readAllBytes(Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/doc-string.feature"))); - Feature feature = parser.parse(uri, source, UUID::randomUUID).get(); - Pickle pickle = feature.getPickles().get(0); - List steps = pickle.getSteps(); - - assertAll(() -> { - assertNull(((DocStringArgument) steps.get(0).getArgument()).getContentType()); - assertEquals("text/plain", ((DocStringArgument) steps.get(1).getArgument()).getContentType()); - }); + try (InputStream source = Files.newInputStream( + Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/doc-string.feature"))) { + + Feature feature = parser.parse(uri, source, UUID::randomUUID).get(); + Pickle pickle = feature.getPickles().get(0); + List steps = pickle.getSteps(); + + assertAll(() -> { + assertNull(((DocStringArgument) steps.get(0).getArgument()).getContentType()); + assertEquals("text/plain", ((DocStringArgument) steps.get(1).getArgument()).getContentType()); + }); + } } @Test void backgrounds_can_occur_twice() throws IOException { - URI uri = URI.create("classpath:com/example.feature"); - String source = new String( - readAllBytes(Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/background.feature"))); - Feature feature = parser.parse(uri, source, UUID::randomUUID).get(); - Pickle pickle = feature.getPickles().get(0); - List steps = pickle.getSteps(); - assertEquals(3, steps.size()); + try (InputStream source = Files.newInputStream( + Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/background.feature"))) { + Feature feature = parser.parse(uri, source, UUID::randomUUID).get(); + Pickle pickle = feature.getPickles().get(0); + List steps = pickle.getSteps(); + assertEquals(3, steps.size()); + } } @Test void lexer_error_throws_exception() throws IOException { - URI uri = URI.create("classpath:com/example.feature"); - String source = new String( - readAllBytes(Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/lexer-error.feature"))); - FeatureParserException exception = assertThrows(FeatureParserException.class, - () -> parser.parse(uri, source, UUID::randomUUID)); - assertEquals("" + - "Failed to parse resource at: classpath:com/example.feature\n" + - "(1:1): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Feature FA'\n" + - "(3:3): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Scenario SA'\n" + - "(4:5): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Given GA'\n" + - "(5:5): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'When GA'\n" + - "(6:5): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Then TA'", - exception.getMessage()); + try (InputStream source = Files.newInputStream( + Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/lexer-error.feature"))) { + FeatureParserException exception = assertThrows(FeatureParserException.class, + () -> parser.parse(uri, source, UUID::randomUUID)); + assertEquals("" + + "Failed to parse resource at: classpath:com/example.feature\n" + + "(1:1): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Feature FA'\n" + + "(3:3): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Scenario SA'\n" + + "(4:5): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Given GA'\n" + + "(5:5): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'When GA'\n" + + "(6:5): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Then TA'", + exception.getMessage()); + } } } diff --git a/cucumber-gherkin/pom.xml b/cucumber-gherkin/pom.xml index b7b4d78311..a88101eb91 100644 --- a/cucumber-gherkin/pom.xml +++ b/cucumber-gherkin/pom.xml @@ -8,6 +8,7 @@ + 5.9.1 io.cucumber.core.gherkin @@ -24,6 +25,13 @@ pom import + + org.junit + junit-bom + ${junit-jupiter.version} + pom + import + @@ -32,6 +40,11 @@ io.cucumber cucumber-plugin + + org.junit.jupiter + junit-jupiter + test + diff --git a/cucumber-gherkin/src/main/java/io/cucumber/core/gherkin/FeatureParser.java b/cucumber-gherkin/src/main/java/io/cucumber/core/gherkin/FeatureParser.java index 14e5b5b4ad..89230527f2 100644 --- a/cucumber-gherkin/src/main/java/io/cucumber/core/gherkin/FeatureParser.java +++ b/cucumber-gherkin/src/main/java/io/cucumber/core/gherkin/FeatureParser.java @@ -1,14 +1,32 @@ package io.cucumber.core.gherkin; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.util.Optional; import java.util.UUID; import java.util.function.Supplier; +import static java.nio.charset.StandardCharsets.UTF_8; + public interface FeatureParser { + @Deprecated Optional parse(URI path, String source, Supplier idGenerator); + default Optional parse(URI path, InputStream source, Supplier idGenerator) throws IOException { + final byte[] buffer = new byte[2 * 1024]; // 2KB + int read; + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + while (-1 != (read = source.read(buffer, 0, buffer.length))) { + outputStream.write(buffer, 0, read); + } + String s = new String(outputStream.toByteArray(), UTF_8); + return parse(path, s, idGenerator); + } + } + String version(); } diff --git a/cucumber-gherkin/src/test/java/io/cucumber/core/gherkin/FeatureParserTest.java b/cucumber-gherkin/src/test/java/io/cucumber/core/gherkin/FeatureParserTest.java new file mode 100644 index 0000000000..36a3d6958c --- /dev/null +++ b/cucumber-gherkin/src/test/java/io/cucumber/core/gherkin/FeatureParserTest.java @@ -0,0 +1,50 @@ +package io.cucumber.core.gherkin; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class FeatureParserTest { + + @Test + void test() throws IOException { + AtomicReference receivedPath = new AtomicReference<>(); + AtomicReference recievedSource = new AtomicReference<>(); + AtomicReference> recievedIdGenerator = new AtomicReference<>(); + + FeatureParser parser = new FeatureParser() { + @Override + public Optional parse(URI path, String source, Supplier idGenerator) { + receivedPath.set(path); + recievedSource.set(source); + recievedIdGenerator.set(idGenerator); + return Optional.empty(); + } + + @Override + public String version() { + return "Test"; + } + }; + URI path = URI.create("classpath:com/example.feature"); + String source = "# comment"; + Supplier idGenerator = UUID::randomUUID; + parser.parse(path, new ByteArrayInputStream(source.getBytes(UTF_8)), idGenerator); + assertEquals(path, receivedPath.get()); + assertEquals(source, recievedSource.get()); + assertEquals(idGenerator, recievedIdGenerator.get()); + + } + +}