Skip to content

Commit d6f5327

Browse files
committed
[JSON] Add basic test cases
1 parent b8ae7a6 commit d6f5327

File tree

1 file changed

+208
-0
lines changed

1 file changed

+208
-0
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
@_spi(PluginMessage) import SwiftCompilerPluginMessageHandling
14+
import XCTest
15+
16+
final class JSONTests: XCTestCase {
17+
18+
func testEmptyStruct() {
19+
let value = EmptyStruct()
20+
_testRoundTrip(of: value, expectedJSON: "{}")
21+
}
22+
23+
func testEmptyClass() {
24+
let value = EmptyClass()
25+
_testRoundTrip(of: value, expectedJSON: "{}")
26+
}
27+
28+
func testTrivialEnumDefault() {
29+
_testRoundTrip(of: Direction.left, expectedJSON: #"{"left":{}}"#)
30+
_testRoundTrip(of: Direction.right, expectedJSON: #"{"right":{}}"#)
31+
}
32+
33+
func testTrivialEnumRawValue() {
34+
_testRoundTrip(of: Animal.dog, expectedJSON: #""dog""#)
35+
_testRoundTrip(of: Animal.cat, expectedJSON: #""cat""#)
36+
}
37+
38+
func testTrivialEnumCustom() {
39+
_testRoundTrip(of: Switch.off, expectedJSON: "false")
40+
_testRoundTrip(of: Switch.on, expectedJSON: "true")
41+
}
42+
43+
func testEnumWithAssociated() {
44+
let tree: Tree = .dictionary([
45+
"name": .string("John Doe"),
46+
"data": .array([.int(12), .string("foo")]),
47+
])
48+
_testRoundTrip(
49+
of: tree,
50+
expectedJSON: #"""
51+
{"dictionary":{"_0":{"data":{"array":{"_0":[{"int":{"_0":12}},{"string":{"_0":"foo"}}]}},"name":{"string":{"_0":"John Doe"}}}}}
52+
"""#
53+
)
54+
}
55+
56+
func testArrayOfInt() {
57+
let arr: [Int] = [12, 42]
58+
_testRoundTrip(of: arr, expectedJSON: "[12,42]")
59+
let empty: [Int] = []
60+
_testRoundTrip(of: empty, expectedJSON: "[]")
61+
}
62+
63+
func testComplexStruct() {
64+
let empty = ComplexStruct(result: nil, diagnostics: [])
65+
_testRoundTrip(of: empty, expectedJSON: #"{"diagnostics":[]}"#)
66+
67+
let value = ComplexStruct(
68+
result: "\tresult\nfoo",
69+
diagnostics: [
70+
.init(
71+
message: "error 🛑",
72+
animal: .cat,
73+
data: [nil, 42]
74+
)
75+
]
76+
)
77+
_testRoundTrip(
78+
of: value,
79+
expectedJSON: #"""
80+
{"diagnostics":[{"animal":"cat","data":[null,42],"message":"error 🛑"}],"result":"\tresult\nfoo"}
81+
"""#
82+
)
83+
}
84+
85+
func testTypeCoercion() {
86+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int].self)
87+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int8].self)
88+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int16].self)
89+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int32].self)
90+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int64].self)
91+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt].self)
92+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt8].self)
93+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt16].self)
94+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt32].self)
95+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt64].self)
96+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Float].self)
97+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Double].self)
98+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int], as: [Bool].self)
99+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int8], as: [Bool].self)
100+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int16], as: [Bool].self)
101+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int32], as: [Bool].self)
102+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int64], as: [Bool].self)
103+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt], as: [Bool].self)
104+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt8], as: [Bool].self)
105+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt16], as: [Bool].self)
106+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt32], as: [Bool].self)
107+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt64], as: [Bool].self)
108+
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Float], as: [Bool].self)
109+
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Double], as: [Bool].self)
110+
}
111+
112+
private func _testRoundTrip<T: Codable & Equatable>(of value: T, expectedJSON: String? = nil) {
113+
let payload: [UInt8]
114+
do {
115+
payload = try JSON.encode(value)
116+
} catch let error {
117+
XCTFail("Failed to encode \(T.self) to JSON: \(error)")
118+
return
119+
}
120+
121+
if let expectedJSON {
122+
let jsonStr = String(decoding: payload, as: UTF8.self)
123+
XCTAssertEqual(jsonStr, expectedJSON)
124+
}
125+
126+
let decoded: T
127+
do {
128+
decoded = try payload.withUnsafeBufferPointer {
129+
try JSON.decode(T.self, from: $0)
130+
}
131+
} catch let error {
132+
XCTFail("Failed to decode \(T.self) from JSON: \(error)")
133+
return
134+
}
135+
XCTAssertEqual(value, decoded)
136+
}
137+
138+
private func _testRoundTripTypeCoercionFailure<T, U>(of value: T, as type: U.Type) where T: Codable, U: Codable {
139+
do {
140+
let data = try JSONEncoder().encode(value)
141+
let _ = try JSONDecoder().decode(U.self, from: data)
142+
XCTFail("Coercion from \(T.self) to \(U.self) was expected to fail.")
143+
} catch {}
144+
}
145+
}
146+
147+
// MARK: - Test Types
148+
149+
fileprivate struct EmptyStruct: Codable, Equatable {
150+
static func == (_ lhs: EmptyStruct, _ rhs: EmptyStruct) -> Bool {
151+
return true
152+
}
153+
}
154+
155+
fileprivate class EmptyClass: Codable, Equatable {
156+
static func == (_ lhs: EmptyClass, _ rhs: EmptyClass) -> Bool {
157+
return true
158+
}
159+
}
160+
161+
fileprivate enum Direction: Codable {
162+
case right
163+
case left
164+
}
165+
166+
fileprivate enum Animal: String, Codable {
167+
case dog
168+
case cat
169+
}
170+
171+
fileprivate enum Switch: Codable {
172+
case off
173+
case on
174+
175+
init(from decoder: Decoder) throws {
176+
let container = try decoder.singleValueContainer()
177+
switch try container.decode(Bool.self) {
178+
case false: self = .off
179+
case true: self = .on
180+
}
181+
}
182+
183+
func encode(to encoder: Encoder) throws {
184+
var container = encoder.singleValueContainer()
185+
switch self {
186+
case .off: try container.encode(false)
187+
case .on: try container.encode(true)
188+
}
189+
}
190+
}
191+
192+
fileprivate enum Tree: Codable, Equatable {
193+
indirect case int(Int)
194+
indirect case string(String)
195+
indirect case array([Self])
196+
indirect case dictionary([String: Self])
197+
}
198+
199+
fileprivate struct ComplexStruct: Codable, Equatable {
200+
struct Diagnostic: Codable, Equatable {
201+
var message: String
202+
var animal: Animal
203+
var data: [Int?]
204+
}
205+
206+
var result: String?
207+
var diagnostics: [Diagnostic]
208+
}

0 commit comments

Comments
 (0)