Skip to content

Issue79 #116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 17, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

### Bugfixes

None yet
*Fixed suggested step definition when step sentence contains double quote ([#79](https://github.com/cucumber/cucumber-cpp/issues/79))

## [0.3.1](https://github.com/cucumber/cucumber-cpp/compare/v0.3...v0.3.1) (11 April 2016)

Expand Down
40 changes: 40 additions & 0 deletions features/specific/escaping.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Feature: Escaping

In order to copy and paste the undefined steps snippets
As a developer
I want the regex string to be correctly escaped

Scenario Outline: <characters> in step definition
Given a scenario with:
"""
Given the step contains '<scenario step string>'
"""
And the steps have no mappings
When Cucumber executes the scenario
Then a step definition snippet with "^the step contains '<step definition string>'$" is suggested

# Remember that in Gherkin's data tables:
# \n -> newline
# \| -> |
# \\ -> \
# \<other> -> <other>
#
# Unfortunately the behaviour is just odd when chaining backslashes:
# \\\\\\. -> \\. (most common case)
# \\\\\\\\+ -> \\+
# \\\\\\\\\\\\\\ -> \\\\

Examples:
| characters | scenario step string | step definition string |
| Double quotes | " | \\" |
| Backslash | \\ | \\\\\\\\\\\\\\ |
| Dot | . | \\\\\\. |
| Caret | ^ | \\\\\\^ |
| Dollar | $ | \\\\\\$ |
| Asterisk | * | \\\\\\* |
| Plus | + | \\\\\\\\+ |
| Question mark | ? | \\\\\\? |
| Brackets | ( ) | \\\\\\( \\\\\\) |
| Square brackets | [ ] | \\\\\\[ \\\\\\] |
| Curly brackets | { } | \\\\\\{ \\\\\\} |
| Pipe | \| | \\\\\\\| |
3 changes: 3 additions & 0 deletions features/step_definitions/cucumber_cpp_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@
expect(@steps_out.gets).to eq(output)
end

Then /^a step definition snippet with (".*") is suggested$/ do |regex_string|
assert_partial_output("(#{regex_string}) {", all_output)
end
6 changes: 5 additions & 1 deletion include/cucumber-cpp/internal/CukeCommands.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ class CukeCommands {
public:
CukeCommands();
virtual ~CukeCommands();

void beginScenario(const TagExpression::tag_list *tags);
void endScenario();
const std::string snippetText(const std::string stepKeyword, const std::string stepName) const;
MatchResult stepMatches(const std::string description) const;
InvokeResult invoke(step_id_type id, const InvokeArgs * pArgs);

protected:
const std::string escapeRegex(const std::string regex) const;
const std::string escapeCString(const std::string str) const;

private:
StepManager stepManager;
HookRegistrar hookRegistrar;
Expand Down
21 changes: 16 additions & 5 deletions src/CukeCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,22 @@ void CukeCommands::endScenario() {
}

const std::string CukeCommands::snippetText(const std::string stepKeyword, const std::string stepName) const {
std::stringstream snippetText; // TODO Escape stepName
snippetText << boost::to_upper_copy(stepKeyword) << "(\"^" << stepName << "$\") {" << std::endl;
snippetText << " pending();" << std::endl;
snippetText << "}" << std::endl;
return snippetText.str();
std::stringstream text;
text << boost::to_upper_copy(stepKeyword)
<< "(\""
<< escapeCString("^" + escapeRegex(stepName) + "$")
<< "\") {\n"
<< " pending();\n"
<< "}\n";
return text.str();
}

const std::string CukeCommands::escapeRegex(const std::string reg) const {
return regex_replace(reg, boost::regex("[\\|\\(\\)\\[\\]\\{\\}\\^\\$\\*\\+\\?\\.\\\\]"), "\\\\&", boost::match_default | boost::format_sed);
}

const std::string CukeCommands::escapeCString(const std::string str) const {
return regex_replace(str, boost::regex("[\"\\\\]"), "\\\\&", boost::match_default | boost::format_sed);
}

MatchResult CukeCommands::stepMatches(const std::string description) const {
Expand Down
19 changes: 18 additions & 1 deletion tests/unit/CukeCommandsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class CheckAllParametersWithMacro : public CheckAllParameters {

TEST_F(CukeCommandsTest, matchesCorrectly) {
addStepWithMatcher(STATIC_MATCHER);
MatchResult result = cukeCommands.stepMatches(STATIC_MATCHER);
MatchResult result = stepMatches(STATIC_MATCHER);
EXPECT_EQ(stepInfoPtr->id, result.getResultSet().at(0).stepInfo->id);
}

Expand All @@ -96,3 +96,20 @@ TEST_F(CukeCommandsTest, invokeHandlesParametersWithMacro) {
// The real test is in CheckAllParameters::body()
runStepBodyTest<CheckAllParametersWithMacro>();
}

TEST_F(CukeCommandsTest, producesSnippetsEscapingTitle) {
EXPECT_EQ("THEN(\"^x\\\\|y\\\"z$\") {\n"
" pending();\n"
"}\n",
snippetText("then","x|y\"z"));
}

TEST_F(CukeCommandsTest, escapesCaractersInRegexes) {
// abc|()[]{}^$*+?.\def <= abc\|\(\)\[\]\{\}\^\$\*\+\?\.\\def
EXPECT_EQ("abc\\|\\(\\)\\[\\]\\{\\}\\^\\$\\*\\+\\?\\.\\\\def", escapeRegex("abc|()[]{}^$*+?.\\def"));
}

TEST_F(CukeCommandsTest, escapesCharactersInCStrings) {
// abc\"def\\ghi <= abc"def\ghi
EXPECT_EQ("abc\\\"def\\\\ghi", escapeCString("abc\"def\\ghi"));
}
5 changes: 2 additions & 3 deletions tests/utils/CukeCommandsFixture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,20 @@ class EmptyStep : public GenericStep {
void body() {}
};

class CukeCommandsFixture : public ::testing::Test {
class CukeCommandsFixture : public ::testing::Test, public CukeCommands {
StepManagerTestDouble stepManager;
public:
const static std::string STATIC_MATCHER;

protected:
CukeCommands cukeCommands;
shared_ptr<StepInfo> stepInfoPtr;

template<class T>
void runStepBodyTest() {
addStepToManager<T>(STATIC_MATCHER);
const InvokeArgs *pArgs = T::buildInvokeArgs();
shared_ptr<const InvokeArgs> spArgs(pArgs);
cukeCommands.invoke(stepInfoPtr->id, pArgs);
invoke(stepInfoPtr->id, pArgs);
}

template<class T>
Expand Down
8 changes: 4 additions & 4 deletions tests/utils/HookRegistrationFixture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,20 @@ class HookRegistrationTest : public CukeCommandsFixture {
}

void beginScenario(const TagExpression::tag_list *tags) {
cukeCommands.beginScenario(tags);
CukeCommandsFixture::beginScenario(tags);
}

void beginScenario(const TagExpression::tag_list & tags) {
TagExpression::tag_list *pTags = new TagExpression::tag_list(tags.begin(), tags.end());
beginScenario(pTags);
CukeCommandsFixture::beginScenario(pTags);
}

void invokeStep() {
cukeCommands.invoke(stepInfoPtr->id, &NO_INVOKE_ARGS);
invoke(stepInfoPtr->id, &NO_INVOKE_ARGS);
}

void endScenario() {
cukeCommands.endScenario();
CukeCommandsFixture::endScenario();
}

std::string sort(std::string str) {
Expand Down