Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
C
capnproto
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
capnproto
Commits
517aaa22
Commit
517aaa22
authored
Jan 01, 2015
by
Kenton Varda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement test filtering.
parent
6cd25260
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
268 additions
and
32 deletions
+268
-32
Makefile.am
c++/Makefile.am
+1
-0
CMakeLists.txt
c++/src/kj/CMakeLists.txt
+1
-0
test-test.c++
c++/src/kj/test-test.c++
+83
-0
test.c++
c++/src/kj/test.c++
+158
-31
test.h
c++/src/kj/test.h
+25
-1
No files found.
c++/Makefile.am
View file @
517aaa22
...
...
@@ -400,6 +400,7 @@ capnp_test_SOURCES = \
src/kj/mutex-test.c++
\
src/kj/threadlocal-test.c++
\
src/kj/threadlocal-pthread-test.c++
\
src/kj/test-test.c++
\
src/capnp/common-test.c++
\
src/capnp/blob-test.c++
\
src/capnp/endian-test.c++
\
...
...
c++/src/kj/CMakeLists.txt
View file @
517aaa22
...
...
@@ -111,6 +111,7 @@ if(BUILD_TESTING)
io-test.c++
mutex-test.c++
threadlocal-test.c++
test-test.c++
std/iostream-test.c++
)
# TODO: Link with librt on Solaris for sched_yield
...
...
c++/src/kj/test-test.c++
0 → 100644
View file @
517aaa22
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "common.h"
#include "test.h"
namespace
kj
{
namespace
_
{
namespace
{
KJ_TEST
(
"GlobFilter"
)
{
{
GlobFilter
filter
(
"foo"
);
KJ_EXPECT
(
filter
.
matches
(
"foo"
));
KJ_EXPECT
(
!
filter
.
matches
(
"bar"
));
KJ_EXPECT
(
!
filter
.
matches
(
"foob"
));
KJ_EXPECT
(
!
filter
.
matches
(
"foobbb"
));
KJ_EXPECT
(
!
filter
.
matches
(
"fobbbb"
));
KJ_EXPECT
(
!
filter
.
matches
(
"bfoo"
));
KJ_EXPECT
(
!
filter
.
matches
(
"bbbbbfoo"
));
KJ_EXPECT
(
filter
.
matches
(
"bbbbb/foo"
));
KJ_EXPECT
(
filter
.
matches
(
"bar/baz/foo"
));
}
{
GlobFilter
filter
(
"foo*"
);
KJ_EXPECT
(
filter
.
matches
(
"foo"
));
KJ_EXPECT
(
!
filter
.
matches
(
"bar"
));
KJ_EXPECT
(
filter
.
matches
(
"foob"
));
KJ_EXPECT
(
filter
.
matches
(
"foobbb"
));
KJ_EXPECT
(
!
filter
.
matches
(
"fobbbb"
));
KJ_EXPECT
(
!
filter
.
matches
(
"bfoo"
));
KJ_EXPECT
(
!
filter
.
matches
(
"bbbbbfoo"
));
KJ_EXPECT
(
filter
.
matches
(
"bbbbb/foo"
));
KJ_EXPECT
(
filter
.
matches
(
"bar/baz/foo"
));
}
{
GlobFilter
filter
(
"foo*bar"
);
KJ_EXPECT
(
filter
.
matches
(
"foobar"
));
KJ_EXPECT
(
filter
.
matches
(
"fooxbar"
));
KJ_EXPECT
(
filter
.
matches
(
"fooxxxbar"
));
KJ_EXPECT
(
!
filter
.
matches
(
"foo/bar"
));
KJ_EXPECT
(
filter
.
matches
(
"blah/fooxxxbar"
));
KJ_EXPECT
(
!
filter
.
matches
(
"blah/xxfooxxxbar"
));
}
{
GlobFilter
filter
(
"foo?bar"
);
KJ_EXPECT
(
!
filter
.
matches
(
"foobar"
));
KJ_EXPECT
(
filter
.
matches
(
"fooxbar"
));
KJ_EXPECT
(
!
filter
.
matches
(
"fooxxxbar"
));
KJ_EXPECT
(
!
filter
.
matches
(
"foo/bar"
));
KJ_EXPECT
(
filter
.
matches
(
"blah/fooxbar"
));
KJ_EXPECT
(
!
filter
.
matches
(
"blah/xxfooxbar"
));
}
}
}
// namespace
}
// namespace _
}
// namespace kj
c++/src/kj/test.c++
View file @
517aaa22
...
...
@@ -39,7 +39,7 @@ TestCase** testCasesTail = &testCasesHead;
TestCase
::
TestCase
(
const
char
*
file
,
uint
line
,
const
char
*
description
)
:
file
(
file
),
line
(
line
),
description
(
description
),
next
(
nullptr
),
prev
(
testCasesTail
),
shouldRun
(
tru
e
)
{
matchedFilter
(
fals
e
)
{
*
prev
=
this
;
testCasesTail
=
&
next
;
}
...
...
@@ -55,6 +55,95 @@ TestCase::~TestCase() {
// =======================================================================================
namespace
_
{
// private
GlobFilter
::
GlobFilter
(
const
char
*
pattern
)
:
pattern
(
heapString
(
pattern
))
{}
GlobFilter
::
GlobFilter
(
ArrayPtr
<
const
char
>
pattern
)
:
pattern
(
heapString
(
pattern
))
{}
bool
GlobFilter
::
matches
(
StringPtr
name
)
{
// Get out your computer science books. We're implementing a non-deterministic finite automaton.
//
// Our NDFA has one "state" corresponding to each character in the pattern.
//
// As you may recall, an NDFA can be transformed into a DFA where every state in the DFA
// represents some combination of states in the NDFA. Therefore, we actually have to store a
// list of states here. (Actually, what we really want is a set of states, but because our
// patterns are mostly non-cyclic a list of states should work fine and be a bit more efficient.)
// Our state list starts out pointing only at the start of the pattern.
states
.
resize
(
0
);
states
.
add
(
0
);
Vector
<
uint
>
scratch
;
// Iterate through each character in the name.
for
(
char
c
:
name
)
{
// Pull the current set of states off to the side, so that we can populate `states` with the
// new set of states.
Vector
<
uint
>
oldStates
=
kj
::
mv
(
states
);
states
=
kj
::
mv
(
scratch
);
states
.
resize
(
0
);
// The pattern can omit a leading path. So if we're at a '/' then enter the state machine at
// the beginning on the next char.
if
(
c
==
'/'
||
c
==
'\\'
)
{
states
.
add
(
0
);
}
// Process each state.
for
(
uint
state
:
oldStates
)
{
applyState
(
c
,
state
);
}
// Store the previous state vector for reuse.
scratch
=
kj
::
mv
(
oldStates
);
}
// If any one state is at the end of the pattern (or at a wildcard just before the end of the
// pattern), we have a match.
for
(
uint
state
:
states
)
{
while
(
state
<
pattern
.
size
()
&&
pattern
[
state
]
==
'*'
)
{
++
state
;
}
if
(
state
==
pattern
.
size
())
{
return
true
;
}
}
return
false
;
}
void
GlobFilter
::
applyState
(
char
c
,
int
state
)
{
if
(
state
<
pattern
.
size
())
{
switch
(
pattern
[
state
])
{
case
'*'
:
// At a '*', we both re-add the current state and attempt to match the *next* state.
if
(
c
!=
'/'
&&
c
!=
'\\'
)
{
// '*' doesn't match '/'.
states
.
add
(
state
);
}
applyState
(
c
,
state
+
1
);
break
;
case
'?'
:
// A '?' matches one character (never a '/').
if
(
c
!=
'/'
&&
c
!=
'\\'
)
{
states
.
add
(
state
+
1
);
}
break
;
default
:
// Any other character matches only itself.
if
(
c
==
pattern
[
state
])
{
states
.
add
(
state
+
1
);
}
break
;
}
}
}
}
// namespace _ (private)
// =======================================================================================
namespace
{
void
crashHandler
(
int
signo
,
siginfo_t
*
info
,
void
*
context
)
{
...
...
@@ -132,12 +221,11 @@ public:
text
=
kj
::
heapString
(
"expectation failed"
);
}
text
=
kj
::
str
(
kj
::
repeat
(
'_'
,
contextDepth
),
file
,
':'
,
line
,
": "
,
kj
::
mv
(
text
),
"
\n
stack: "
,
strArray
(
trace
,
" "
),
stringifyStackTrace
(
trace
));
text
=
kj
::
str
(
kj
::
repeat
(
'_'
,
contextDepth
),
file
,
':'
,
line
,
": "
,
kj
::
mv
(
text
));
if
(
severity
==
LogSeverity
::
ERROR
||
severity
==
LogSeverity
::
FATAL
)
{
sawError
=
true
;
context
.
error
(
text
);
context
.
error
(
kj
::
str
(
text
,
"
\n
stack: "
,
strArray
(
trace
,
" "
),
stringifyStackTrace
(
trace
))
);
}
else
{
context
.
warning
(
text
);
}
...
...
@@ -158,38 +246,73 @@ public:
}
MainFunc
getMain
()
{
// TODO(now): Include summary of tests.
return
MainBuilder
(
context
,
"(no version)"
,
"Runs some tests
."
)
.
addOptionWithArg
({
'
t'
,
"test-case"
},
KJ_BIND_METHOD
(
*
this
,
setTestCase
),
"<file>[:<line>]"
,
return
MainBuilder
(
context
,
"KJ Test Runner (version not applicable)"
,
"Run all tests that have been linked into the binary with this test runner
."
)
.
addOptionWithArg
({
'
f'
,
"filter"
},
KJ_BIND_METHOD
(
*
this
,
setFilter
),
"<file>[:<line>]"
,
"Run only the specified test case(s). You may use a '*' wildcard in <file>. You may "
"also omit any prefix of <file>'s path; test from all matching files will run."
)
"also omit any prefix of <file>'s path; test from all matching files will run. "
"You may specify multiple filters; any test matching at least one filter will run. "
"<line> may be a range, e.g.
\"
100-500
\"
."
)
.
addOption
({
'l'
,
"list"
},
KJ_BIND_METHOD
(
*
this
,
setList
),
"List all test cases that would run, but don't run them. If --filter is specified "
"then only the match tests will be listed."
)
.
callAfterParsing
(
KJ_BIND_METHOD
(
*
this
,
run
))
.
build
();
}
MainBuilder
::
Validity
setTestCase
(
StringPtr
pattern
)
{
MainBuilder
::
Validity
setFilter
(
StringPtr
pattern
)
{
hasFilter
=
true
;
ArrayPtr
<
const
char
>
filePattern
=
pattern
;
kj
::
Maybe
<
uint
>
lineNumber
=
nullptr
;
uint
minLine
=
kj
::
minValue
;
uint
maxLine
=
kj
::
maxValue
;
KJ_IF_MAYBE
(
colonPos
,
pattern
.
findLast
(
':'
))
{
char
*
end
;
StringPtr
lineStr
=
pattern
.
slice
(
*
colonPos
+
1
);
lineNumber
=
strtoul
(
lineStr
.
cStr
(),
&
end
,
0
);
if
(
lineStr
.
size
()
>
0
&&
*
end
==
'\0'
)
{
bool
parsedRange
=
false
;
minLine
=
strtoul
(
lineStr
.
cStr
(),
&
end
,
0
);
if
(
end
!=
lineStr
.
begin
())
{
if
(
*
end
==
'-'
)
{
// A range.
const
char
*
part2
=
end
+
1
;
maxLine
=
strtoul
(
part2
,
&
end
,
0
);
if
(
end
>
part2
&&
*
end
==
'\0'
)
{
parsedRange
=
true
;
}
}
else
if
(
*
end
==
'\0'
)
{
parsedRange
=
true
;
}
}
if
(
parsedRange
)
{
// We have an exact line number.
filePattern
=
pattern
.
slice
(
0
,
*
colonPos
);
}
else
{
// Can't parse as a number. Maybe the colon is part of a Windows path name or something.
// Let's just keep it as part of the file pattern.
lineNumber
=
nullptr
;
minLine
=
kj
::
minValue
;
maxLine
=
kj
::
maxValue
;
}
}
// TODO(now): do the filter
_
::
GlobFilter
filter
(
filePattern
);
for
(
TestCase
*
testCase
=
testCasesHead
;
testCase
!=
nullptr
;
testCase
=
testCase
->
next
)
{
if
(
!
testCase
->
matchedFilter
&&
filter
.
matches
(
testCase
->
file
)
&&
testCase
->
line
>=
minLine
&&
testCase
->
line
<=
maxLine
)
{
testCase
->
matchedFilter
=
true
;
}
}
return
true
;
}
MainBuilder
::
Validity
setList
()
{
listOnly
=
true
;
return
true
;
}
MainBuilder
::
Validity
run
()
{
if
(
testCasesHead
==
nullptr
)
{
return
"no tests were declared"
;
...
...
@@ -207,7 +330,7 @@ public:
}
// Back off the prefix to the last '/'.
while
(
commonPrefix
.
size
()
>
0
&&
commonPrefix
.
back
()
!=
'/'
)
{
while
(
commonPrefix
.
size
()
>
0
&&
commonPrefix
.
back
()
!=
'/'
&&
commonPrefix
.
back
()
!=
'\\'
)
{
commonPrefix
=
commonPrefix
.
slice
(
0
,
commonPrefix
.
size
()
-
1
);
}
...
...
@@ -215,27 +338,29 @@ public:
uint
passCount
=
0
;
uint
failCount
=
0
;
for
(
TestCase
*
testCase
=
testCasesHead
;
testCase
!=
nullptr
;
testCase
=
testCase
->
next
)
{
if
(
testCase
->
shouldRun
)
{
if
(
!
hasFilter
||
testCase
->
matchedFilter
)
{
auto
name
=
kj
::
str
(
testCase
->
file
+
commonPrefix
.
size
(),
':'
,
testCase
->
line
,
": "
,
testCase
->
description
);
write
(
BLUE
,
"[ TEST ]"
,
name
);
bool
currentFailed
=
true
;
KJ_IF_MAYBE
(
exception
,
runCatchingExceptions
([
&
]()
{
TestExceptionCallback
exceptionCallback
(
context
);
testCase
->
run
();
currentFailed
=
exceptionCallback
.
failed
();
}))
{
context
.
error
(
kj
::
str
(
*
exception
));
}
if
(
currentFailed
)
{
write
(
RED
,
"[ FAIL ]"
,
name
);
++
failCount
;
}
else
{
write
(
GREEN
,
"[ PASS ]"
,
name
);
++
passCount
;
if
(
!
listOnly
)
{
bool
currentFailed
=
true
;
KJ_IF_MAYBE
(
exception
,
runCatchingExceptions
([
&
]()
{
TestExceptionCallback
exceptionCallback
(
context
);
testCase
->
run
();
currentFailed
=
exceptionCallback
.
failed
();
}))
{
context
.
error
(
kj
::
str
(
*
exception
));
}
if
(
currentFailed
)
{
write
(
RED
,
"[ FAIL ]"
,
name
);
++
failCount
;
}
else
{
write
(
GREEN
,
"[ PASS ]"
,
name
);
++
passCount
;
}
}
}
}
...
...
@@ -248,6 +373,8 @@ public:
private
:
ProcessContext
&
context
;
bool
useColor
;
bool
hasFilter
=
false
;
bool
listOnly
=
false
;
enum
Color
{
RED
,
...
...
c++/src/kj/test.h
View file @
517aaa22
...
...
@@ -27,6 +27,7 @@
#endif
#include "debug.h"
#include "vector.h"
namespace
kj
{
...
...
@@ -45,7 +46,7 @@ private:
const
char
*
description
;
TestCase
*
next
;
TestCase
**
prev
;
bool
shouldRun
;
bool
matchedFilter
;
friend
class
TestRunner
;
};
...
...
@@ -72,6 +73,29 @@ private:
if (cond); else KJ_FAIL_EXPECT("failed: expected " #cond, ##__VA_ARGS__)
#endif
// =======================================================================================
namespace
_
{
// private
class
GlobFilter
{
// Implements glob filters for the --filter flag.
//
// Exposed in header only for testing.
public
:
explicit
GlobFilter
(
const
char
*
pattern
);
explicit
GlobFilter
(
ArrayPtr
<
const
char
>
pattern
);
bool
matches
(
StringPtr
name
);
private
:
String
pattern
;
Vector
<
uint
>
states
;
void
applyState
(
char
c
,
int
state
);
};
}
// namespace _ (private)
}
// namespace kj
#endif // KJ_TEST_H_
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment