Commit f9212cd1 authored by Drew's avatar Drew

Improve statictool parser

* Support more advanced test layouts, like nested/namespaced test classes, from the static-tool parser.
* Tests are now ordered alphabetically
* dev: statictool-check now provides test coverage for statictool and is run on 'check'
parent 1fc00948
Pipeline #2729 failed with stages
in 53 seconds
......@@ -15,7 +15,7 @@
:payloads [
{
:key "git"
:used-commit "53ef640390d549803cf2bed1ba2dba919345d513"
:used-commit "ff7a35214bc982966a2b07ddd8ffaa7b548da43a"
:pin false
}
]
......
......@@ -3,7 +3,8 @@
:version "1.0"
:external-packages [
{
:version [">=0.1"]
:branch "master"
;;:version [">=0.1"]
:url "https://code.sealedabstract.com/drewcrawford/StandBack.git"
}
]
......@@ -20,6 +21,11 @@
:link-with-product ["StandBack.a"]
}
:statictool-check {
:tool "shell"
:script "tests/caroline-static-tool-tests/test.sh"
}
:statictool-atbin {
:tool "packageatbin"
:name "caroline-static-tool"
......@@ -46,7 +52,7 @@
:debug {
:dependencies ["statictool"]
:tool "shell"
:script ".atllbuild/products/caroline-static-tool --core caroline-static-tool-tests/fixtures/SampleTests/foo.swift"
:script ".atllbuild/products/caroline-static-tool --core tests/caroline-static-tool-tests/fixtures/foo.swift"
}
:core {
:tool "atllbuild"
......@@ -140,7 +146,7 @@
}
:check {
:dependencies ["simpletest" "coretest" "osx-xcode-toolrun"]
:dependencies ["statictool-check" "simpletest" "coretest" "osx-xcode-toolrun"]
:tool "nop"
}
}
......
......@@ -22,4 +22,14 @@ extension String {
}
return true
}
}
extension String.UTF8View {
func substring(from: String.UTF8View.Index) -> String {
return String(describing: self[from..<self.endIndex])
}
func substring(from: Int) -> String {
let index = self.index(startIndex, offsetBy: from)
return substring(from: index)
}
}
\ No newline at end of file
......@@ -14,6 +14,9 @@
import StandBack
//bug: don't support unicode
private let identifierPattern = "[[:alnum:]]+"
func findTests(_ sourceFiles: [String]) -> [String] {
var tests: [String] = []
for file in sourceFiles {
......@@ -37,14 +40,92 @@ func findTests(_ sourceFiles: [String]) -> [String] {
fatalError("\(error)")
}
}
for match in try! Regex(pattern: "class[[:space:]]+([[:alnum:]]+)[[:space:]]*:[[:space:]]*CarolineTest[[:space:]]*\\{").findAll(inString: sourceText) {
let className = match.groups[0]!
tests.append(className.description)
tests.append(contentsOf: findTests(sourceText: sourceText))
}
return tests.sorted(by: {$0 < $1})
}
private struct NamedScope {
let name: String
let start: Int
let end: Int
let underlyingString: String
var description: String {
let utf8 = self.underlyingString.utf8
return String(describing: utf8[utf8.index(utf8.startIndex, offsetBy: start)..<utf8.index(utf8.startIndex, offsetBy:end)])
}
//[0]: type
//[1]: identifier
private static let namedScopeBeginRegex = try! Regex(pattern: "(class|enum|struct|extension)[[:space:]]+(\(identifierPattern))[^\\{]*{")
static func parse(sourceText: String) -> [NamedScope] {
var scopes: [NamedScope] = []
for namedScope in NamedScope.namedScopeBeginRegex.findAll(inString: sourceText) {
if (scopes.last?.end ?? 0) > namedScope.entireMatch.start { continue } //scope is not "flat"
let scopeStart = namedScope.entireMatch.end
let scopeChomp = sourceText.utf8.substring(from: scopeStart)
var indent = 0
var scopeExitPosition: Int? = nil
loop:
for (idx, utf) in scopeChomp.utf8.enumerated() {
switch(utf) {
case "}".utf8.first!:
indent -= 1
if indent == -1 {
scopeExitPosition = idx
break loop
}
case "{".utf8.first!:
indent += 1
default:
break
}
}
guard let scopeExit = scopeExitPosition else { fatalError("Parse error")}
let s = NamedScope(name: namedScope.groups[1]!.description, start: scopeStart, end: scopeExit + scopeStart, underlyingString: sourceText)
scopes.append(s)
}
return scopes
}
return tests
func overlaps(_ match: Match) -> Bool {
if self.start < match.end && self.end < match.end { return false }
if self.start > match.end && self.end > match.end { return false }
return true
}
}
func findTests(sourceText: String, namespace: String? = nil) -> [String] {
var tests: [String] = []
let scopes = NamedScope.parse(sourceText: sourceText)
for scope in scopes {
let fqdn: String
if let n = namespace {
fqdn = "\(n).\(scope.name)"
}
else {
fqdn = scope.name
}
tests.append(contentsOf: findTests(sourceText: scope.description, namespace: fqdn))
}
for match in try! Regex(pattern: "class[[:space:]]+(\(identifierPattern))[[:space:]]*:[[:space:]]*CarolineTest[[:space:]]*\\{").findAll(inString: sourceText) {
let className = match.groups[0]!.description
//is this match inside one of our scopes?
let matchingScopes = scopes.filter({$0.overlaps(match.entireMatch) && className != $0.name})
if matchingScopes.count > 0 {
continue
}
let fqdn: String
if let n = namespace {
fqdn = "\(n).\(className)"
}
else {
fqdn = className
}
tests.append(fqdn)
}
return tests
}
func allTestsDeclaration(tests: [String], indentation: Int) -> String {
......
......@@ -17,17 +17,17 @@
// This file is automatically generated by Caroline and should not be edited by hand.
import CarolineCore
let allTests: [CarolineTest] = [
AssertTrue(),
AssertFalse(),
AssertTrue(),
DictEqual(),
DictEqualInverse(),
DictEqualLengthMismatch(),
DictNotEqual(),
DictNotEqualInverse(),
DictNotEqualLengthMismatch(),
NoErrorCheck(),
ErrorCheck(),
Fail(),
NoErrorCheck(),
SequenceEqual(),
SequenceEqualInverse(),
SequenceEqualLengthA(),
......
......@@ -17,8 +17,8 @@
// This file is automatically generated by Caroline and should not be edited by hand.
import CarolineCore
let allTests: [CarolineTest] = [
Simple(),
Additional()
Additional(),
Simple()
]
let engine = CarolineCoreEngine()
if !engine.testAll(allTests) {
......
// Copyright (c) 2016 Drew Crawford.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file is automatically generated by Caroline and should not be edited by hand.
import CarolineCore
let allTests: [CarolineTest] = [
A(),
B(),
C(),
D(),
E(),
F(),
G(),
H(),
I(),
Z.J(),
Z.K()
]
let engine = CarolineCoreEngine()
if !engine.testAll(allTests) {
fatalError("Caroline tests failed")
}
// Copyright (c) 2016 Drew Crawford.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file is automatically generated by Caroline and should not be edited by hand.
import CarolineCore
let allTests: [CarolineTest] = [
NestMe1.TestNested(),
NestMe2.TestNested(),
NestMe3.NestMeInception.TestNestedC(),
NestMe3.TestNestedA(),
NestMe3.TestNestedB(),
Test1(),
Test2(),
Test3()
]
let engine = CarolineCoreEngine()
if !engine.testAll(allTests) {
fatalError("Caroline tests failed")
}
/*
Copyright 2016 Drew Crawford
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
protocol JunkProtocol { }
protocol JunkProtocol2 { }
class Test1: CarolineTest { }
struct NestMe1 {
class TestNested: CarolineTest {
func test() {}
}
}
class NestMe2: JunkProtocol, JunkProtocol2 {
class TestNested: CarolineTest {
func test() { }
}
}
enum NestMe3 {
class TestNestedA: CarolineTest {
func test() { }
}
}
class Test2: CarolineTest {
func test() { }
}
extension NestMe3 {
class TestNestedB: CarolineTest {
func test() { }
}
}
extension NestMe3 {
class NestMeInception {
class TestNestedC: CarolineTest {
func test() { }
}
}
}
class Test3: CarolineTest { }
#!/bin/bash
set -e
for FILE in tests/caroline-static-tool-tests/fixtures/*.swift
do
echo "Processing file $FILE"
EXPECTATION=`echo "$FILE" | sed 's+/fixtures/+/expectations/+g'`
diff $EXPECTATION <(.atllbuild/products/caroline-static-tool --core $FILE)
done
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment