diff --git a/KissMe/Sources/Test/KissTestCase.swift b/KissMe/Sources/Test/KissTestCase.swift new file mode 100644 index 0000000..f1b39fc --- /dev/null +++ b/KissMe/Sources/Test/KissTestCase.swift @@ -0,0 +1,44 @@ +// +// KissTestCase.swift +// KissMe +// +// Created by ened-book-m1 on 11/8/24. +// + +public protocol KissTestCase { + // Method called before each test + func setUp() + + // Method called after each test + func tearDown() + + // Method to run all tests + func runTests() +} + +extension KissTestCase { + public func setUp() {} + public func tearDown() {} + + public func runTests() { + let mirror = Mirror(reflecting: self) + for child in mirror.children { + if let name = child.label, name.hasPrefix("test"), + let test = child.value as? () -> Void { + setUp() + print("\(name) begin") + test() + print("\(name) end") + tearDown() + } + } + } +} + +public func assertEqual(_ a: T, _ b: T, message: String = "") { + if a != b { + print("Assertion failed: \(a) is not equal to \(b). \(message)") + } else { + //print("Test passed.") + } +} diff --git a/KissMeConsole/Sources/KissConsole+DB.swift b/KissMeConsole/Sources/KissConsole+DB.swift index a6d8846..7fc96a4 100644 --- a/KissMeConsole/Sources/KissConsole+DB.swift +++ b/KissMeConsole/Sources/KissConsole+DB.swift @@ -10,47 +10,6 @@ import KissMe import KissMeme -extension KissConsole { -} - -func test_build_min_db() { -// guard let enumerator = FileManager.subPathFiles("data") else { -// return -// } - - //let db = try KissDB(directory: url) - - //test_check_name_parsed() - //test_date_time() - //build_min_db_from_candle_csv() - test_select_min_db() -} - -private func test_field_type() { - let v1: Int64 = 0 - let v2: Int64 = 1 - let v3: Int64 = 65536 - let v4: Int64 = -2 - - print("\(v1.fieldType)") - print("\(v2.fieldType)") - print("\(v3.fieldType)") - print("\(v4.fieldType)") - print("done") -} - -private func test_date_time() { - let kissDate = Date.date(yyyyMMdd: "20200101", HHmmss: "000000") - let timestamp = UInt64(kissDate!.timeIntervalSince1970) - print("timestamp: \(timestamp)") - print("kissDate: \(kissDate!.timeIntervalSince2020)") - print("today: \(UInt32(Date().timeIntervalSince2020))") - - let value: UInt64 = 1234567890 - let d = Data(value: value) - print(d.hexString) -} - enum CandleDataFieldType: UInt8 { case uint8 = 1 // 8 bits unsigned integer case uint16 = 2 // 16 bits unsigned integer @@ -60,6 +19,20 @@ enum CandleDataFieldType: UInt8 { case float = 11 // 4 byte float point } +extension CandleDataFieldType: CustomStringConvertible { + var description: String { + switch self { + case .uint8: return "uint8" + case .uint16: return "uint16" + case .uint32: return "uint32" + case .uint64: return "uint64" + case .double: return "double" + case .float: return "float" + } + } +} + + extension Int64 { var fieldType: CandleDataFieldType { let unsignedValue = UInt64(bitPattern: self) @@ -89,6 +62,7 @@ extension Int64 { } } + extension Domestic.Candle: @retroactive Equatable { public static func == (lhs: Domestic.Candle, rhs: Domestic.Candle) -> Bool { return @@ -103,6 +77,7 @@ extension Domestic.Candle: @retroactive Equatable { } } + struct CandleData { let key: Data let data: Data @@ -142,7 +117,7 @@ struct CandleData { let values = [accumulatedTradingAmount, currentStockPrice, stockOpenningPrice, highestStockPrice, lowestStockPrice, conclusionVolume] - var typeFields = [UInt8]() + var typeFields = [UInt8]() var valuesData = Data() for value in values { let valueData: Data @@ -165,7 +140,7 @@ struct CandleData { data.append(contentsOf: typeFields) data.append(valuesData) self.data = data - print("data: \(data.count)") + //print("data: \(data.count)") } var candle: Domestic.Candle { @@ -177,19 +152,36 @@ struct CandleData { let typeFields = [UInt8](data[0 ..< 6]) var values = [stockBusinessDate, stockConclusionTime] - print("candle data: \(data.count)") + //print("candle data: \(data.count)") var start = 6 for field in typeFields { let value: String switch CandleDataFieldType(rawValue: field)! { - case .uint8: value = String(data.subdata(in: start ..< start+1).value_UInt8); start += 1 - case .uint16: value = String(data.subdata(in: start ..< start+2).value_UInt16); start += 2 - case .uint32: value = String(data.subdata(in: start ..< start+4).value_UInt32); start += 4 - case .uint64: value = String(data.subdata(in: start ..< start+8).value_UInt64); start += 8 - case .float: value = String(data.subdata(in: start ..< start+4).value_Float); start += 4 - case .double: value = String(data.subdata(in: start ..< start+8).value_Double); start += 8 + case .uint8: + value = String(data.subdata(in: start ..< start+1).value_UInt8) + start += 1 + + case .uint16: + value = String(data.subdata(in: start ..< start+2).value_UInt16) + start += 2 + + case .uint32: + value = String(data.subdata(in: start ..< start+4).value_UInt32) + start += 4 + + case .uint64: + value = String(data.subdata(in: start ..< start+8).value_UInt64) + start += 8 + + case .float: + value = String(data.subdata(in: start ..< start+4).value_Float) + start += 4 + + case .double: + value = String(data.subdata(in: start ..< start+8).value_Double) + start += 8 } values.append(value) } @@ -197,117 +189,8 @@ struct CandleData { } } -private func build_min_db(_ productNo: String, _ candle_csvs: [URL]) { - let dataPath = URL.currentDirectory().appending(path: "data") - - for csvUrl in candle_csvs { - let candleMinName = CandleMinuteFileName() - if let (_, yyyyMMdd) = candleMinName.matchedUrl(csvUrl.path), let year = Int(yyyyMMdd.prefix(4)) { - let yearDbPath = dataPath.appending(path: "\(productNo)/min/candle-\(year).db1") - //try? FileManager.default.removeItem(at: directory) - try? FileManager.default.createDirectory(at: yearDbPath, withIntermediateDirectories: true) - - do { - let candles = try [Domestic.Candle].readCsv(fromFile: csvUrl) - - let db = try KissDB(directory: yearDbPath) - try db.begin() - - for candle in candles { - let candleData = try CandleData(candle: candle) - let item = KissDB.DataItem(key: candleData.key, value: candleData.data) - try db.insert(item: item) - - if candleData.candle != candle { - assertionFailure("invalid candle data") - } - } - - try db.commit() - } catch { - print("\(error)") - return - } - } - } -} - -private func build_min_db_from_candle_csv() { - guard let enumerator = FileManager.subPathFiles("data") else { - return - } - - var lastProductNo: String? - - let candleMinName = CandleMinuteFileName() - var allCandles = [String: [URL]]() - for case let fileUrl as URL in enumerator { - guard let (productNo, yyyyMMdd) = candleMinName.matchedUrl(fileUrl.path) else { - continue - } - - // Select only one product no - if lastProductNo == nil { - lastProductNo = productNo - } - else { - if lastProductNo! != productNo { - break - } - } - - if allCandles.keys.contains(productNo) { - allCandles[productNo]!.append(fileUrl) - } - else { - allCandles[productNo] = [fileUrl] - } - print("product: \(productNo) \(yyyyMMdd)") - } - print("total \(allCandles.count)") - - if let productCandles = allCandles.first { - build_min_db(productCandles.key, productCandles.value) - } -} - - -private func test_select_min_db() { - let yearDbPath = URL(filePath: "/Users/ened/Kiss/KissMe/bin/data/047040/min/candle-2023.db1") - do { - let startTime = KissDB.appTime - - let db = try KissDB(directory: yearDbPath) - try db.begin() - - try db.select(into: { (dataItem: KissDB.DataItem) -> Bool in - //let candleData = CandleData(key: dataItem.key, data: dataItem.value) - //print("\(candleData.candleDate) : \(candleData.candle.accumulatedTradingAmount)") - return true - }) - - try db.rollback() - - let endTime = KissDB.appTime - print("DB count: \(db.count) insertAll elapsed: \(endTime - startTime)") - } catch { - print("\(error)") - } -} - - -private func test_check_name_parsed() { - let candleMinName = CandleMinuteFileName() - let url = "/Users/ened/Kiss/KissMe/bin/data/000020/min/candle-20230705.csv" - guard let (productNo, yyyyMMdd) = candleMinName.matchedUrl(url) else { - return - } - print(productNo, yyyyMMdd) -} - class CandleMinuteFileName { - let regex: NSRegularExpression init() { @@ -333,3 +216,66 @@ class CandleMinuteFileName { return nil } } + + +extension KissConsole { + + func collectCandleMinuteFiles() -> [String: [URL]] { + guard let enumerator = FileManager.subPathFiles("data") else { + return [:] + } + + let candleMinName = CandleMinuteFileName() + var allCandles = [String: [URL]]() + for case let fileUrl as URL in enumerator { + guard let (productNo, _) = candleMinName.matchedUrl(fileUrl.path) else { + continue + } + + if allCandles.keys.contains(productNo) { + allCandles[productNo]!.append(fileUrl) + } + else { + allCandles[productNo] = [fileUrl] + } + } + return allCandles + } + + func buildCandleMinuteDB(productNo: String, csvFiles: [URL], removeOldDB: Bool = false) { + let dataPath = URL.currentDirectory().appending(path: "data") + + for csvFile in csvFiles { + let candleMinName = CandleMinuteFileName() + if let (_, yyyyMMdd) = candleMinName.matchedUrl(csvFile.path), let year = Int(yyyyMMdd.prefix(4)) { + let yearDbPath = dataPath.appending(path: "\(productNo)/min/candle-\(year).db1") + if removeOldDB { + try? FileManager.default.removeItem(at: yearDbPath) + } + try? FileManager.default.createDirectory(at: yearDbPath, withIntermediateDirectories: true) + + do { + let candles = try [Domestic.Candle].readCsv(fromFile: csvFile) + + let db = try KissDB(directory: yearDbPath) + try db.begin() + + for candle in candles { + let candleData = try CandleData(candle: candle) + let item = KissDB.DataItem(key: candleData.key, value: candleData.data) + try db.insertData(item: item) + + if candleData.candle != candle { + assertionFailure("invalid candle data") + } + } + + try db.commit() + } catch { + print("\(error)") + return + } + } + } + } +} diff --git a/KissMeConsole/Sources/Tests/DB1_CandleData_Tests.swift b/KissMeConsole/Sources/Tests/DB1_CandleData_Tests.swift new file mode 100644 index 0000000..50ea619 --- /dev/null +++ b/KissMeConsole/Sources/Tests/DB1_CandleData_Tests.swift @@ -0,0 +1,129 @@ +// +// DB1_CandleData_Tests.swift +// KissMeConsole +// +// Created by ened-book-m1 on 11/8/24. +// + +import Foundation +import KissMe +import KissMeme + +struct DB1_CandleData_Tests: KissTestCase { + + let test_1_FieldType = { + let v1: Int64 = 0 + let v2: Int64 = 1 + let v3: Int64 = 65536 + let v4: Int64 = -2 + + assertEqual(v1.fieldType, .uint8) + assertEqual(v2.fieldType, .uint8) + assertEqual(v3.fieldType, .uint32) + assertEqual(v4.fieldType, .uint64) + } + + let test_2_DateTimeSince2020 = { + let kissDate = Date.date(yyyyMMdd: "20200101", HHmmss: "000000") + let timestamp = UInt64(kissDate!.timeIntervalSince1970) + + assertEqual(kissDate!.timeIntervalSince2020, 0) + + let today = Date() + let todayTimestampSince2020 = UInt64(today.timeIntervalSince2020) + let diffTimeStamp = UInt64(today.timeIntervalSince1970) - timestamp + assertEqual(todayTimestampSince2020, diffTimeStamp) + } + + let test_3_ParseCandleCsvFileName = { + let candleMinName = CandleMinuteFileName() + let url = "/Users/ened/Kiss/KissMe/bin/data/000020/min/candle-20230705.csv" + guard let (productNo, yyyyMMdd) = candleMinName.matchedUrl(url) else { + return + } + assertEqual(productNo, "000020") + assertEqual(yyyyMMdd, "20230705") + } + + let test_4_BuildSamsungDB = { + let productNo = Self.samsungProductNo + let year = Self.year + let month = Self.month + let csvFiles = Self.collectCsv(productNo: productNo, yyyy: year, MM: month) + let dataPath = URL.currentDirectory().appending(path: "data") + + for csvFile in csvFiles { + let candleMinName = CandleMinuteFileName() + if let (_, yyyyMMdd) = candleMinName.matchedUrl(csvFile.path), let year = Int(yyyyMMdd.prefix(4)) { + let yearDbPath = dataPath.appending(path: "\(productNo)/min/candle-\(year).db1") + } + + let yearDbPath = dataPath.appending(path: "\(productNo)/min/candle-\(year).db1") + try? FileManager.default.removeItem(at: yearDbPath) + try? FileManager.default.createDirectory(at: yearDbPath, withIntermediateDirectories: true) + + do { + let candles = try [Domestic.Candle].readCsv(fromFile: csvFile) + + let db = try KissDB(directory: yearDbPath) + try db.begin() + + for candle in candles { + let candleData = try CandleData(candle: candle) + let item = KissDB.DataItem(key: candleData.key, value: candleData.data) + try db.insertData(item: item) + + assertEqual(candleData.candle, candle) + } + + try db.commit() + } catch { + print("\(error)") + } + } + } + + let test_5_CountSamsungDB: () -> Void = { + let productNo = Self.samsungProductNo + let year = Self.year + let yyyyMM = String(year) + Self.month + let dataPath = URL.currentDirectory().appending(path: "data") + + let yearDbPath = dataPath.appending(path: "\(productNo)/min/candle-\(year).db1") + do { + let db = try KissDB(directory: yearDbPath) + try db.begin() + + try db.selectData(into: { (dataItem: KissDB.DataItem) -> Bool in + let candleData = CandleData(key: dataItem.key, data: dataItem.value) + assertEqual(String(candleData.candleDate.prefix(6)), yyyyMM) + return true + }) + + try db.rollback() + } catch { + print("\(error)") + } + } + + static let samsungProductNo = "005930" + static let year = "2023" + static let month = "06" + + static func collectCsv(productNo: String, yyyy: String, MM: String) -> [URL] { + guard let enumerator = FileManager.subPathFiles("data/\(productNo)") else { + return [] + } + let candleMinName = CandleMinuteFileName() + var csvFiles = [URL]() + for case let fileUrl as URL in enumerator { + guard let (fileProductNo, yyyyMMdd) = candleMinName.matchedUrl(fileUrl.path) else { + continue + } + if fileProductNo == productNo, yyyyMMdd.prefix(4) == yyyy, yyyyMMdd.dropFirst(4).prefix(2) == MM { + csvFiles.append(fileUrl) + } + } + return csvFiles + } +} diff --git a/KissMeConsole/Sources/main.swift b/KissMeConsole/Sources/main.swift index d9679d9..8be3d8c 100644 --- a/KissMeConsole/Sources/main.swift +++ b/KissMeConsole/Sources/main.swift @@ -7,7 +7,7 @@ import Foundation -KissConsole().run() +//KissConsole().run() import KissMe @@ -17,4 +17,10 @@ import KissMe //test_websocket_dump_data() //test_auction() -//test_build_min_db() + +func runTests() { + let tests = DB1_CandleData_Tests() + tests.runTests() +} + +runTests() diff --git a/libraries/KissMeme b/libraries/KissMeme index 7769eb3..db14c42 160000 --- a/libraries/KissMeme +++ b/libraries/KissMeme @@ -1 +1 @@ -Subproject commit 7769eb3e89c8c28c94a20999f1a17ba96488d672 +Subproject commit db14c42c4d6e0218682a1257eeaeae0768060d17 diff --git a/projects/macos/KissMe.xcodeproj/project.pbxproj b/projects/macos/KissMe.xcodeproj/project.pbxproj index d1584ef..4099f95 100644 --- a/projects/macos/KissMe.xcodeproj/project.pbxproj +++ b/projects/macos/KissMe.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ 341F5F112A1685E700962D48 /* ShopRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F102A1685E700962D48 /* ShopRequest.swift */; }; 341F5F142A16CD7A00962D48 /* DomesticShopProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F132A16CD7A00962D48 /* DomesticShopProduct.swift */; }; 3435A7F72A35D82000D604F1 /* DomesticShortSelling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3435A7F62A35D82000D604F1 /* DomesticShortSelling.swift */; }; + 346C25DA2CDE1D97003EF8D7 /* KissTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346C25D92CDE1D91003EF8D7 /* KissTestCase.swift */; }; 34942F562CCA9AD200F85B79 /* DomesticStock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34942F552CCA9AD200F85B79 /* DomesticStock.swift */; }; 34942F5A2CCA9B2700F85B79 /* DomesticFutures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34942F592CCA9B2700F85B79 /* DomesticFutures.swift */; }; 349B05172C25B7C600378D55 /* OverseasStockResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349B05162C25B7C600378D55 /* OverseasStockResult.swift */; }; @@ -183,6 +184,7 @@ 341F5F102A1685E700962D48 /* ShopRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopRequest.swift; sourceTree = ""; }; 341F5F132A16CD7A00962D48 /* DomesticShopProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticShopProduct.swift; sourceTree = ""; }; 3435A7F62A35D82000D604F1 /* DomesticShortSelling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticShortSelling.swift; sourceTree = ""; }; + 346C25D92CDE1D91003EF8D7 /* KissTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissTestCase.swift; sourceTree = ""; }; 34942F552CCA9AD200F85B79 /* DomesticStock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticStock.swift; sourceTree = ""; }; 34942F592CCA9B2700F85B79 /* DomesticFutures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticFutures.swift; sourceTree = ""; }; 349B05162C25B7C600378D55 /* OverseasStockResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverseasStockResult.swift; sourceTree = ""; }; @@ -329,6 +331,7 @@ 341F5EAD2A0A80EC00962D48 /* KissMe */ = { isa = PBXGroup; children = ( + 346C25D82CDE1D7A003EF8D7 /* Test */, 34F1900D2A426C1C0068C697 /* Context */, 34F1900A2A41981A0068C697 /* Index */, 341F5EF32A0F88AC00962D48 /* Common */, @@ -416,6 +419,14 @@ path = Shop; sourceTree = ""; }; + 346C25D82CDE1D7A003EF8D7 /* Test */ = { + isa = PBXGroup; + children = ( + 346C25D92CDE1D91003EF8D7 /* KissTestCase.swift */, + ); + path = Test; + sourceTree = ""; + }; 34942F582CCA9B1000F85B79 /* Futures */ = { isa = PBXGroup; children = ( @@ -827,6 +838,7 @@ files = ( 341F5EFB2A10909D00962D48 /* LoginResult.swift in Sources */, 34BC447D2A86635A0052D8EB /* Domestic.ContractPriceWebSocket.swift in Sources */, + 346C25DA2CDE1D97003EF8D7 /* KissTestCase.swift in Sources */, 34C1BA882A5D9A4A00423D64 /* DomesticDartDisclosureInterests.swift in Sources */, 340A4DC42A4E4345005A1FBA /* ArrayDecodable.swift in Sources */, 34C1BA532A5A683D00423D64 /* DomesticDartBusinessReport.swift in Sources */, diff --git a/projects/macos/KissMeConsole.xcodeproj/project.pbxproj b/projects/macos/KissMeConsole.xcodeproj/project.pbxproj index ec4b26c..a8b590c 100644 --- a/projects/macos/KissMeConsole.xcodeproj/project.pbxproj +++ b/projects/macos/KissMeConsole.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 341F5F092A1463A100962D48 /* KissConsole.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F082A1463A100962D48 /* KissConsole.swift */; }; 3435A7F22A35A8A900D604F1 /* KissConsole+Investor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3435A7F12A35A8A900D604F1 /* KissConsole+Investor.swift */; }; 3435A7F42A35B4D000D604F1 /* KissConsole+Price.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3435A7F32A35B4D000D604F1 /* KissConsole+Price.swift */; }; + 346C25DD2CDE5652003EF8D7 /* DB1_CandleData_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346C25DC2CDE5652003EF8D7 /* DB1_CandleData_Tests.swift */; }; 348168492A2F92AC00A50BD3 /* KissContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348168482A2F92AC00A50BD3 /* KissContext.swift */; }; 349327F72A20E3E300097063 /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349327F62A20E3E300097063 /* Foundation+Extensions.swift */; }; 349843212A242AC900E85B08 /* KissConsole+CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349843202A242AC900E85B08 /* KissConsole+CSV.swift */; }; @@ -59,6 +60,7 @@ 341F5F082A1463A100962D48 /* KissConsole.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissConsole.swift; sourceTree = ""; }; 3435A7F12A35A8A900D604F1 /* KissConsole+Investor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KissConsole+Investor.swift"; sourceTree = ""; }; 3435A7F32A35B4D000D604F1 /* KissConsole+Price.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KissConsole+Price.swift"; sourceTree = ""; }; + 346C25DC2CDE5652003EF8D7 /* DB1_CandleData_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DB1_CandleData_Tests.swift; sourceTree = ""; }; 348168482A2F92AC00A50BD3 /* KissContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissContext.swift; sourceTree = ""; }; 349327F62A20E3E300097063 /* Foundation+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+Extensions.swift"; sourceTree = ""; }; 3498431E2A24287600E85B08 /* KissMeConsoleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KissMeConsoleTests.swift; sourceTree = ""; }; @@ -106,6 +108,7 @@ 341F5ED22A0A8B9000962D48 /* KissMeConsole */ = { isa = PBXGroup; children = ( + 346C25DB2CDE2A7F003EF8D7 /* Tests */, 341F5ED32A0A8B9000962D48 /* main.swift */, 341F5F042A13B82F00962D48 /* test.swift */, 34DA3EA32A9A176B00BB3439 /* test_websocket.swift */, @@ -134,6 +137,14 @@ name = Frameworks; sourceTree = ""; }; + 346C25DB2CDE2A7F003EF8D7 /* Tests */ = { + isa = PBXGroup; + children = ( + 346C25DC2CDE5652003EF8D7 /* DB1_CandleData_Tests.swift */, + ); + path = Tests; + sourceTree = ""; + }; 3498431D2A24284600E85B08 /* KissMeConsoleTests */ = { isa = PBXGroup; children = ( @@ -211,6 +222,7 @@ 349843212A242AC900E85B08 /* KissConsole+CSV.swift in Sources */, 348168492A2F92AC00A50BD3 /* KissContext.swift in Sources */, 34DB3C452AA6071D00B6763E /* KissConsole+WebSocket.swift in Sources */, + 346C25DD2CDE5652003EF8D7 /* DB1_CandleData_Tests.swift in Sources */, 3435A7F42A35B4D000D604F1 /* KissConsole+Price.swift in Sources */, 34DA3EA42A9A176B00BB3439 /* test_websocket.swift in Sources */, 34F190132A4441F00068C697 /* KissConsole+Test.swift in Sources */,