Skip to content

Commit 9b3225f

Browse files
committed
Merge #116 fixes #79
2 parents bbf75ca + fbc49a3 commit 9b3225f

File tree

8 files changed

+89
-15
lines changed

8 files changed

+89
-15
lines changed

HISTORY.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
### Bugfixes
99

10-
None yet
10+
* Fixed suggested step definition when step sentence contains double quote ([#116](https://github.com/cucumber/cucumber-cpp/issues/116) Kamil Strzempowicz, [fbc49a3](https://github.com/cucumber/cucumber-cpp/commit/fbc49a34e12a0b9b2a6e121d97ba1ad8f46dce8f) Paolo Ambrosio)
1111

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

features/specific/escaping.feature

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Feature: Escaping
2+
3+
In order to copy and paste the undefined steps snippets
4+
As a developer
5+
I want the regex string to be correctly escaped
6+
7+
Scenario Outline: <characters> in step definition
8+
Given a scenario with:
9+
"""
10+
Given the step contains '<scenario step string>'
11+
"""
12+
And the steps have no mappings
13+
When Cucumber executes the scenario
14+
Then a step definition snippet with "^the step contains '<step definition string>'$" is suggested
15+
16+
# Remember that in Gherkin's data tables:
17+
# \n -> newline
18+
# \| -> |
19+
# \\ -> \
20+
# \<other> -> <other>
21+
#
22+
# Unfortunately the behaviour is just odd when chaining backslashes:
23+
# \\\\\\. -> \\. (most common case)
24+
# \\\\\\\\+ -> \\+
25+
# \\\\\\\\\\\\\\ -> \\\\
26+
27+
Examples:
28+
| characters | scenario step string | step definition string |
29+
| Double quotes | " | \\" |
30+
| Backslash | \\ | \\\\\\\\\\\\\\ |
31+
| Dot | . | \\\\\\. |
32+
| Caret | ^ | \\\\\\^ |
33+
| Dollar | $ | \\\\\\$ |
34+
| Asterisk | * | \\\\\\* |
35+
| Plus | + | \\\\\\\\+ |
36+
| Question mark | ? | \\\\\\? |
37+
| Brackets | ( ) | \\\\\\( \\\\\\) |
38+
| Square brackets | [ ] | \\\\\\[ \\\\\\] |
39+
| Curly brackets | { } | \\\\\\{ \\\\\\} |
40+
| Pipe | \| | \\\\\\\| |

features/step_definitions/cucumber_cpp_steps.rb

+3
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@
66
expect(@steps_out.gets).to eq(output)
77
end
88

9+
Then /^a step definition snippet with (".*") is suggested$/ do |regex_string|
10+
assert_partial_output("(#{regex_string}) {", all_output)
11+
end

include/cucumber-cpp/internal/CukeCommands.hpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@ class CukeCommands {
2525
public:
2626
CukeCommands();
2727
virtual ~CukeCommands();
28-
28+
2929
void beginScenario(const TagExpression::tag_list *tags);
3030
void endScenario();
3131
const std::string snippetText(const std::string stepKeyword, const std::string stepName) const;
3232
MatchResult stepMatches(const std::string description) const;
3333
InvokeResult invoke(step_id_type id, const InvokeArgs * pArgs);
3434

35+
protected:
36+
const std::string escapeRegex(const std::string regex) const;
37+
const std::string escapeCString(const std::string str) const;
38+
3539
private:
3640
StepManager stepManager;
3741
HookRegistrar hookRegistrar;

src/CukeCommands.cpp

+16-5
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,22 @@ void CukeCommands::endScenario() {
3434
}
3535

3636
const std::string CukeCommands::snippetText(const std::string stepKeyword, const std::string stepName) const {
37-
std::stringstream snippetText; // TODO Escape stepName
38-
snippetText << boost::to_upper_copy(stepKeyword) << "(\"^" << stepName << "$\") {" << std::endl;
39-
snippetText << " pending();" << std::endl;
40-
snippetText << "}" << std::endl;
41-
return snippetText.str();
37+
std::stringstream text;
38+
text << boost::to_upper_copy(stepKeyword)
39+
<< "(\""
40+
<< escapeCString("^" + escapeRegex(stepName) + "$")
41+
<< "\") {\n"
42+
<< " pending();\n"
43+
<< "}\n";
44+
return text.str();
45+
}
46+
47+
const std::string CukeCommands::escapeRegex(const std::string reg) const {
48+
return regex_replace(reg, boost::regex("[\\|\\(\\)\\[\\]\\{\\}\\^\\$\\*\\+\\?\\.\\\\]"), "\\\\&", boost::match_default | boost::format_sed);
49+
}
50+
51+
const std::string CukeCommands::escapeCString(const std::string str) const {
52+
return regex_replace(str, boost::regex("[\"\\\\]"), "\\\\&", boost::match_default | boost::format_sed);
4253
}
4354

4455
MatchResult CukeCommands::stepMatches(const std::string description) const {

tests/unit/CukeCommandsTest.cpp

+18-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class CheckAllParametersWithMacro : public CheckAllParameters {
8383

8484
TEST_F(CukeCommandsTest, matchesCorrectly) {
8585
addStepWithMatcher(STATIC_MATCHER);
86-
MatchResult result = cukeCommands.stepMatches(STATIC_MATCHER);
86+
MatchResult result = stepMatches(STATIC_MATCHER);
8787
EXPECT_EQ(stepInfoPtr->id, result.getResultSet().at(0).stepInfo->id);
8888
}
8989

@@ -96,3 +96,20 @@ TEST_F(CukeCommandsTest, invokeHandlesParametersWithMacro) {
9696
// The real test is in CheckAllParameters::body()
9797
runStepBodyTest<CheckAllParametersWithMacro>();
9898
}
99+
100+
TEST_F(CukeCommandsTest, producesSnippetsEscapingTitle) {
101+
EXPECT_EQ("THEN(\"^x\\\\|y\\\"z$\") {\n"
102+
" pending();\n"
103+
"}\n",
104+
snippetText("then","x|y\"z"));
105+
}
106+
107+
TEST_F(CukeCommandsTest, escapesCaractersInRegexes) {
108+
// abc|()[]{}^$*+?.\def <= abc\|\(\)\[\]\{\}\^\$\*\+\?\.\\def
109+
EXPECT_EQ("abc\\|\\(\\)\\[\\]\\{\\}\\^\\$\\*\\+\\?\\.\\\\def", escapeRegex("abc|()[]{}^$*+?.\\def"));
110+
}
111+
112+
TEST_F(CukeCommandsTest, escapesCharactersInCStrings) {
113+
// abc\"def\\ghi <= abc"def\ghi
114+
EXPECT_EQ("abc\\\"def\\\\ghi", escapeCString("abc\"def\\ghi"));
115+
}

tests/utils/CukeCommandsFixture.hpp

+2-3
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,20 @@ class EmptyStep : public GenericStep {
1414
void body() {}
1515
};
1616

17-
class CukeCommandsFixture : public ::testing::Test {
17+
class CukeCommandsFixture : public ::testing::Test, public CukeCommands {
1818
StepManagerTestDouble stepManager;
1919
public:
2020
const static std::string STATIC_MATCHER;
2121

2222
protected:
23-
CukeCommands cukeCommands;
2423
shared_ptr<StepInfo> stepInfoPtr;
2524

2625
template<class T>
2726
void runStepBodyTest() {
2827
addStepToManager<T>(STATIC_MATCHER);
2928
const InvokeArgs *pArgs = T::buildInvokeArgs();
3029
shared_ptr<const InvokeArgs> spArgs(pArgs);
31-
cukeCommands.invoke(stepInfoPtr->id, pArgs);
30+
invoke(stepInfoPtr->id, pArgs);
3231
}
3332

3433
template<class T>

tests/utils/HookRegistrationFixture.hpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,20 @@ class HookRegistrationTest : public CukeCommandsFixture {
9999
}
100100

101101
void beginScenario(const TagExpression::tag_list *tags) {
102-
cukeCommands.beginScenario(tags);
102+
CukeCommandsFixture::beginScenario(tags);
103103
}
104104

105105
void beginScenario(const TagExpression::tag_list & tags) {
106106
TagExpression::tag_list *pTags = new TagExpression::tag_list(tags.begin(), tags.end());
107-
beginScenario(pTags);
107+
CukeCommandsFixture::beginScenario(pTags);
108108
}
109109

110110
void invokeStep() {
111-
cukeCommands.invoke(stepInfoPtr->id, &NO_INVOKE_ARGS);
111+
invoke(stepInfoPtr->id, &NO_INVOKE_ARGS);
112112
}
113113

114114
void endScenario() {
115-
cukeCommands.endScenario();
115+
CukeCommandsFixture::endScenario();
116116
}
117117

118118
std::string sort(std::string str) {

0 commit comments

Comments
 (0)