Skip to content

Commit 6025908

Browse files
Merge pull request #193 from wwt/data-driven
Adds the ability to drive workflows from data.
2 parents d2a96d9 + 97758a2 commit 6025908

File tree

64 files changed

+3989
-54
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+3989
-54
lines changed

.github/.jazzy.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ custom_categories:
4040
- UIWorkflowItem
4141
- HostedWorkflowItem
4242
- StoryboardLoadable
43+
- name: Server Driven Workflows
44+
children:
45+
- Generated Type Registry
46+
- JSONDecoder
47+
- DecodeWorkflow
48+
- FlowRepresentableAggregator
49+
- WorkflowDecodable
4350
- name: Controlling Presentation
4451
children:
4552
- LaunchStyle
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
//
2+
// SwiftCurrent_IRGeneratorTests.swift
3+
// SwiftCurrent
4+
//
5+
// Created by Tyler Thompson on 3/3/22.
6+
// Copyright © 2022 WWT and Tyler Thompson. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import ShellOut
11+
import Algorithms
12+
import XCTest
13+
14+
class SwiftCurrent_IRGeneratorTests: XCTestCase {
15+
static var packageSwiftDirectory: URL = {
16+
// ../../../ brings you to SwiftCurrent directory
17+
URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
18+
}()
19+
20+
lazy var generatorCommand: String = {
21+
"\(Self.packageSwiftDirectory.path)/.build/*/debug/SwiftCurrent_CLI generate ir"
22+
}()
23+
24+
override class func setUp() {
25+
XCTAssertNoThrow(try shellOut(to: "rm -rf \(Self.packageSwiftDirectory.path)/.build/*/debug"))
26+
XCTAssert(try shellOut(to: "cd \(Self.packageSwiftDirectory.path) && swift build --product=SwiftCurrent_CLI").contains("Build complete!"))
27+
}
28+
29+
func testSingleDecodableStruct() throws {
30+
let source = """
31+
struct Foo: WorkflowDecodable { }
32+
"""
33+
34+
let output = try shellOut(to: "\(generatorCommand) \"\(source)\"")
35+
let IR = try JSONDecoder().decode([IRType].self, from: XCTUnwrap(output.data(using: .utf8)))
36+
37+
XCTAssertEqual(IR.count, 1)
38+
XCTAssertEqual(IR.first?.name, "Foo")
39+
}
40+
41+
func testSingleDecodableClass() throws {
42+
let source = """
43+
class Foo: WorkflowDecodable { }
44+
"""
45+
46+
let output = try shellOut(to: "\(generatorCommand) \"\(source)\"")
47+
let IR = try JSONDecoder().decode([IRType].self, from: XCTUnwrap(output.data(using: .utf8)))
48+
49+
XCTAssertEqual(IR.count, 1)
50+
XCTAssertEqual(IR.first?.name, "Foo")
51+
}
52+
53+
func testMultipleMixedStructsAndClasses() throws {
54+
let source = """
55+
class Foo: WorkflowDecodable { }
56+
struct Bar: WorkflowDecodable { }
57+
"""
58+
59+
let output = try shellOut(to: "\(generatorCommand) \"\(source)\"")
60+
let IR = try JSONDecoder().decode([IRType].self, from: XCTUnwrap(output.data(using: .utf8)))
61+
.sorted { $0.name < $1.name }
62+
63+
XCTAssertEqual(IR.count, 2)
64+
XCTAssertEqual(IR.first?.name, "Bar")
65+
XCTAssertEqual(IR.last?.name, "Foo")
66+
}
67+
68+
func testOnlyDetectWorkflowDecodableTypes() throws {
69+
let source = """
70+
struct Foo: WorkflowDecodable { }
71+
struct Bar { }
72+
"""
73+
74+
let output = try shellOut(to: "\(generatorCommand) \"\(source)\"")
75+
let IR = try JSONDecoder().decode([IRType].self, from: XCTUnwrap(output.data(using: .utf8)))
76+
77+
XCTAssertEqual(IR.count, 1)
78+
XCTAssertEqual(IR.first?.name, "Foo")
79+
}
80+
81+
func testSingleLayerOfIndirection() throws {
82+
let source = """
83+
protocol Foo: WorkflowDecodable { }
84+
struct Bar: Foo { }
85+
"""
86+
87+
let output = try shellOut(to: "\(generatorCommand) \"\(source)\"")
88+
let IR = try JSONDecoder().decode([IRType].self, from: XCTUnwrap(output.data(using: .utf8)))
89+
90+
XCTAssertEqual(IR.count, 1)
91+
XCTAssertEqual(IR.first?.name, "Bar")
92+
}
93+
94+
func testMultipleLayersOfIndirection() throws {
95+
let source = """
96+
protocol Foo: WorkflowDecodable { }
97+
protocol Bar: Foo { }
98+
struct Baz: Bar { }
99+
"""
100+
101+
let output = try shellOut(to: "\(generatorCommand) \"\(source)\"")
102+
let IR = try JSONDecoder().decode([IRType].self, from: XCTUnwrap(output.data(using: .utf8)))
103+
104+
XCTAssertEqual(IR.count, 1)
105+
XCTAssertEqual(IR.first?.name, "Baz")
106+
}
107+
108+
func testSingleLayerOfNesting() throws {
109+
let source = """
110+
enum Foo {
111+
struct Bar: WorkflowDecodable { }
112+
}
113+
"""
114+
115+
let output = try shellOut(to: "\(generatorCommand) \"\(source)\"")
116+
let IR = try JSONDecoder().decode([IRType].self, from: XCTUnwrap(output.data(using: .utf8)))
117+
118+
XCTAssertEqual(IR.count, 1)
119+
XCTAssertEqual(IR.first?.name, "Foo.Bar")
120+
}
121+
122+
func testMultipleLayersOfNesting() throws {
123+
let source = """
124+
enum Foo {
125+
struct Bar {
126+
class Baz: WorkflowDecodable { }
127+
}
128+
}
129+
"""
130+
131+
let output = try shellOut(to: "\(generatorCommand) \"\(source)\"")
132+
let IR = try JSONDecoder().decode([IRType].self, from: XCTUnwrap(output.data(using: .utf8)))
133+
134+
XCTAssertEqual(IR.count, 1)
135+
XCTAssertEqual(IR.first?.name, "Foo.Bar.Baz")
136+
}
137+
138+
func testConformanceViaExtension() throws {
139+
let source = """
140+
struct Foo { }
141+
142+
extension Foo: WorkflowDecodable { }
143+
"""
144+
145+
let output = try shellOut(to: "\(generatorCommand) \"\(source)\"")
146+
let IR = try JSONDecoder().decode([IRType].self, from: XCTUnwrap(output.data(using: .utf8)))
147+
148+
XCTAssertEqual(IR.count, 1)
149+
XCTAssertEqual(IR.first?.name, "Foo")
150+
}
151+
152+
func testConformanceViaExtension_WithNesting() throws {
153+
let source = """
154+
enum Foo {
155+
struct Bar { }
156+
}
157+
158+
extension Foo.Bar: WorkflowDecodable { }
159+
"""
160+
161+
let output = try shellOut(to: "\(generatorCommand) \"\(source)\"")
162+
let IR = try JSONDecoder().decode([IRType].self, from: XCTUnwrap(output.data(using: .utf8)))
163+
164+
XCTAssertEqual(IR.count, 1)
165+
XCTAssertEqual(IR.first?.name, "Foo.Bar")
166+
}
167+
168+
func testPerformance_WithASingleType() throws {
169+
let source = """
170+
struct Foo: WorkflowDecodable { }
171+
"""
172+
measure {
173+
_ = try? shellOut(to: "\(generatorCommand) \"\(source)\"")
174+
}
175+
}
176+
177+
func testPerformance_WithManyTypes() throws {
178+
struct Structure {
179+
let name: String
180+
let type: String
181+
}
182+
func generateType() -> Structure {
183+
let nominalType = ["struct", "enum", "class"].randomElement()!
184+
let name: String = (Unicode.Scalar("A").value...Unicode.Scalar("Z").value)
185+
.lazy
186+
.compactMap(Unicode.Scalar.init)
187+
.map(String.init)
188+
.filter { _ in Bool.random() }
189+
.joined()
190+
return Structure(name: name, type: nominalType)
191+
}
192+
var typeDefs: [String] = (1...1000).lazy.map { _ -> String in
193+
let type = generateType()
194+
return "\(type.type) \(type.name): WorkflowDecodable { }"
195+
}
196+
.uniqued()
197+
.map { $0 }
198+
199+
if typeDefs.count < 1000 {
200+
(1...(1000 - typeDefs.count)).forEach {
201+
let type = generateType()
202+
typeDefs.append("\(type.type)\($0) \(type.name): WorkflowDecodable { }")
203+
}
204+
}
205+
206+
let source = typeDefs.joined(separator: "\n")
207+
measure {
208+
if let output = try? shellOut(to: "\(generatorCommand) \"\(source)\""),
209+
let data = output.data(using: .utf8) {
210+
let IR = try? JSONDecoder().decode([IRType].self, from: data)
211+
XCTAssertEqual(IR?.count, 1000)
212+
} else {
213+
XCTFail("No output from shell")
214+
}
215+
}
216+
}
217+
}
218+
219+
struct IRType: Decodable {
220+
let name: String
221+
}

0 commit comments

Comments
 (0)