diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index f9f73733de2..384730c9621 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -75,7 +75,7 @@ allprojects { sourceSets { // backend.native/tests testOutputLocal { - output.dir(rootProject.file("$testOutputRoot/local")) + output.dir(rootProject.file("$testOutputRoot/local")) } // backend.native/tests/external @@ -87,6 +87,11 @@ allprojects { testOutputStdlib { output.dir(rootProject.file("$testOutputRoot/stdlib")) } + + // backend.native/tests/framework + testOutputFramework { + output.dir(rootProject.file("$testOutputRoot/framework")) + } } } @@ -152,6 +157,9 @@ def tasksOf(Closure filter) { run { def tasks = tasksOf { it instanceof KonanTest && it.inDevelopersRun } dependsOn(tasks) + + // Add framework tests + dependsOn(project.tasks.withType(FrameworkTest)) } // Collect results in one json. @@ -2645,6 +2653,41 @@ task produce_dynamic(type: DynamicKonanTest) { "topLevel = 777 3\n" } +/** + * Get sourceSet's output path as a string + */ +static String outputPath(SourceSet set) { + return set.output.getDirs().getSingleFile().toString() +} + +if (isMac() && project.testTarget != 'wasm32') { + def target = project.testTarget ?: 'host' + + task testValuesFramework(type: FrameworkTest) { + frameworkName = 'Values' + konanArtifacts { + framework(frameworkName, targets: [ target ]) { + srcDir 'framework/values' + baseDir outputPath(sourceSets.testOutputFramework) + "/$frameworkName" + } + } + swiftSources = ['framework/values/values.swift'] + dependsOn "compileKonan${frameworkName}" + } + + task testStdlibFramework(type: FrameworkTest) { + frameworkName = 'Stdlib' + konanArtifacts { + framework(frameworkName, targets: [ target ]) { + srcDir 'framework/stdlib' + baseDir outputPath(sourceSets.testOutputFramework) + "/$frameworkName" + } + } + swiftSources = ['framework/stdlib/stdlib.swift'] + dependsOn "compileKonan${frameworkName}" + } +} + task buildKonanStdlibTests(type: BuildKonanTest) { outputSourceSetName = "testOutputStdlib" compileList = [ "stdlib_external", "testUtils.kt" ] diff --git a/backend.native/tests/framework/main.swift b/backend.native/tests/framework/main.swift new file mode 100644 index 00000000000..27cdbc4de1e --- /dev/null +++ b/backend.native/tests/framework/main.swift @@ -0,0 +1,155 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * 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. + */ + +import Foundation + +enum TestError : Error { + case assertFailed(String) + case failure + case testsFailed([String]) +} + +// ---------------- Assertions ---------------- + +func assertEquals(actual: T, expected: T, + _ message: String = "Assertion failed:") throws { + if (actual != expected) { + throw TestError.assertFailed(message + " Expected value: \(expected), but got: \(actual)") + } +} + +func assertEquals(actual: [T], expected: [T], + _ message: String = "Assertion failed: arrays not equal") throws { + try assertEquals(actual: actual.count, expected: expected.count, "Size differs") + try assertTrue(actual.elementsEqual(expected), "Arrays elements are not equal") +} + +func assertTrue(_ value: Bool, _ message: String = "Assertion failed:") throws { + if (value != true) { + throw TestError.assertFailed(message + " Expected value to be TRUE, but got: \(value)") + } +} + +func assertFalse(_ value: Bool, _ message: String = "Assertion failed:") throws { + if (value != false) { + throw TestError.assertFailed(message + " Expected value to be FALSE, but got: \(value)") + } +} + + +// ---------------- Utils -------------------- + +func withAutorelease( _ method: @escaping () throws -> Void) -> () throws -> Void { + return { () throws -> Void in + try autoreleasepool { try method() } + } +} + +// ---------------- Execution ---------------- + +private final class Statistics : CustomStringConvertible { + var passed: [String] = [] + var failed: [String] = [] + + var description: String { + return """ + ---- RESULTS: + PASSED: \(passed.count) + FAILED: \(failed.count) + """ + } + + static let instance = Statistics() + + static func getInstance() -> Statistics { + return instance + } + + func start(_ name: String) { + print("---- Starting test: \(name)") + } + + func passed(_ name: String) { + print("---- PASSED test: \(name)") + passed.append(name) + } + + func failed(_ name: String, error: Error) { + print("---- FAILED test: \(name) with error: \(error)") + failed.append(name) + } +} +/** + * TestCase represents a single test + */ +struct TestCase { + let name: String + let method: () throws -> Void + + init(name: String, method: @escaping () throws -> Void) { + self.name = name + self.method = method + } + + func run() { + let stats = Statistics.getInstance() + stats.start(name) + do { + try method() + stats.passed(name) + } catch { + stats.failed(name, error: error) + } + } +} + +protocol TestProvider { + var tests: [TestCase] { get } +} + +var providers: [TestProvider] = [] + +private func execute(tests: [TestCase]) { + for test in tests { + test.run() + } +} + +/** + * Entry point of the test + */ +private func main() { + // Generated method that instantiates TestProvider + registerProvider() + + let stats = Statistics.getInstance() + for pr in providers { + execute(tests: pr.tests) + } + print(stats) + + let failed = stats.failed + if !failed.isEmpty { + print() + print("Tests failed:") + for testName in failed { + print(":: \(testName)") + } + abort() + } +} + +main() \ No newline at end of file diff --git a/backend.native/tests/framework/stdlib/stdlib.kt b/backend.native/tests/framework/stdlib/stdlib.kt new file mode 100644 index 00000000000..1bce6618b17 --- /dev/null +++ b/backend.native/tests/framework/stdlib/stdlib.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * 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. + */ + +@file:Suppress("UNUSED") + +package stdlib + +fun isEmpty(map: Map) = map.isEmpty() + +fun getKeysAsSet(map: Map) = map.keys +fun getKeysAsList(map: Map) = map.keys.toList() + +fun toMutableMap(map: HashMap) = map.toMutableMap() + +fun getFirstElement(collection: Collection) = collection.first() + +class GenericExtensionClass> (private val holder: T?) { + fun getFirstKey(): K? = holder?.entries?.first()?.key + + fun getFirstValue() : V? { + holder?.entries?.forEach { e -> println("KEY: ${e.key} VALUE: ${e.value}") } + return holder?.entries?.first()?.value + } +} + +fun createPair(): + Pair, GenericExtensionClass>> { + val l = createLinkedMap() + val g = GenericExtensionClass(l) + return Pair(l, g) +} + +fun createLinkedMap() = linkedMapOf() + +fun createTypedMutableMap() = linkedMapOf() + +fun addSomeElementsToMap(map: MutableMap) { + map.put(key = "XYZ", value = 321) + map.put(key = "TMP", value = 451) +} + +fun list(vararg elements: Any?): Any = listOf(*elements) +fun set(vararg elements: Any?): Any = setOf(*elements) +fun map(vararg keysAndValues: Any?): Any = mutableMapOf().apply { + (0 until keysAndValues.size step 2).forEach {index -> + this[keysAndValues[index]] = keysAndValues[index + 1] + } +} + +fun emptyMutableList(): Any = mutableListOf() +fun emptyMutableSet(): Any = mutableSetOf() +fun emptyMutableMap(): Any = mutableMapOf() + +data class TripleVals(val first: T, val second: T, val third: T) + +data class TripleVars(var first: T, var second: T, var third: T) { + override fun toString(): String { + return "[$first, $second, $third]" + } +} \ No newline at end of file diff --git a/backend.native/tests/framework/stdlib/stdlib.swift b/backend.native/tests/framework/stdlib/stdlib.swift new file mode 100644 index 00000000000..b6fea6af624 --- /dev/null +++ b/backend.native/tests/framework/stdlib/stdlib.swift @@ -0,0 +1,351 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * 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. + */ + +import Foundation +import Stdlib + +extension NSEnumerator { + func remainingObjects() -> [Any?] { + var result = [Any?]() + while (true) { + if let next = self.nextObject() { + result.append(next as AnyObject as Any?) + } else { + break + } + } + return result + } +} + +class StdlibTests : TestProvider { + var tests: [TestCase] = [] + + init() { + tests = [ + TestCase(name: "TestEmptyDictionary", method: withAutorelease(testEmptyDictionary)), + TestCase(name: "TestGenericMapUsage", method: withAutorelease(testGenericMapUsage)), + TestCase(name: "TestOrderedMapStored", method: withAutorelease(testOrderedMapStored)), + TestCase(name: "TestTypedMapUsage", method: withAutorelease(testTypedMapUsage)), + TestCase(name: "TestFirstElement", method: withAutorelease(testFirstElement)), + TestCase(name: "TestAddDictionary", method: withAutorelease(testAddDictionary)), + TestCase(name: "TestList", method: withAutorelease(testList)), + TestCase(name: "TestMutableList", method: withAutorelease(testMutableList)), + TestCase(name: "TestSet", method: withAutorelease(testSet)), + TestCase(name: "TestMutableSet", method: withAutorelease(testMutableSet)), + TestCase(name: "TestMap", method: withAutorelease(testMap)), + TestCase(name: "TestMutableMap", method: withAutorelease(testMutableMap)) + ] + providers.append(self) + } + + /** + * Pass empty dictionary to Kotlin. + */ + func testEmptyDictionary() throws { + let immutableEmptyDict = [String: Int]() + try assertTrue(Stdlib.isEmpty(map: immutableEmptyDict), "Empty dictionary") + let keys = Stdlib.getKeysAsSet(map: immutableEmptyDict) + try assertTrue(keys.isEmpty, "Should have empty set") + } + + /** + * Tests usage of a map with generics. + */ + func testGenericMapUsage() throws { + let map = Stdlib.createLinkedMap() + map[1] = "One" + map[10] = "Ten" + map[11] = "Eleven" + map["10"] = "Ten as string" + for (k, v) in map { + print("MAP: \(k) - \(v)") + } + + try assertEquals(actual: map[11] as! String, expected: "Eleven", "An element of the map for key: 11") + } + + /** + * Checks order of the underlying LinkedHashMap. + */ + func testOrderedMapStored() throws { + let pair = Stdlib.createPair() + let map = pair.first as? NSMutableDictionary + + map?[1] = "One" + map?[10] = "Ten" + map?[11] = "Eleven" + map?["10"] = "Ten as string" + + let gen = pair.second as! StdlibGenericExtensionClass + let value: String? = gen.getFirstValue() as? String + try assertEquals(actual: value!, expected: "One", "First value of the map") + + let key: Int? = gen.getFirstKey() as? Int + try assertEquals(actual: key!, expected: 1, "First key of the map") + } + + /** + * Tests typed map created in Kotlin. + */ + func testTypedMapUsage() throws { + let map = Stdlib.createTypedMutableMap() + map[1] = "One" + map[1.0 as Float] = "Float" + map[11] = "Eleven" + map["10"] = "Ten as string" + + try assertEquals(actual: map["10"] as! String, expected: "Ten as string", "String key") + try assertEquals(actual: map[1.0 as Float] as! String, expected: "Float", "Float key") + } + + /** + * Get first element of the collection. + */ + func testFirstElement() throws { + let m = Stdlib.createTypedMutableMap() + m[10] = "Str" + try assertEquals(actual: Stdlib.getFirstElement(collection: m.allKeys) as! Int, expected: 10, "First key") + + try assertEquals(actual: Stdlib.getFirstElement(collection: Stdlib.getKeysAsList(map: m as! Dictionary)) as! Int, + expected: 10, "First key from a list") + } + + /** + * Add element to dictionary in Kotlin + */ + func testAddDictionary() throws { + let m = [ "ABC": 10, "CDE": 12, "FGH": 3 ] + Stdlib.addSomeElementsToMap(map: StdlibMutableDictionary(dictionary: m)) + for (k, v) in m { + print("MAP: \(k) - \(v)") + } + + var smd = StdlibMutableDictionary() + smd.setObject(333, forKey: "333" as NSString) + try assertEquals(actual: smd.object(forKey: "333" as NSString) as! Int, expected: 333, "Add element to dict") + + Stdlib.addSomeElementsToMap(map: smd) + for (k, v) in smd { + print("MAP: \(k) - \(v)") + } + try assertEquals(actual: smd.object(forKey: "XYZ" as NSString) as! Int, expected: 321, "Get element from Kotlin") + } + + func zeroTo(_ n: Int32) -> StdlibStdlibArray { return StdlibStdlibArray(size: n) { $0 } } + + func testList() throws { + let elements = zeroTo(5) + elements.set(index: 1, value: nil) + let list = Stdlib.list(elements: elements) as! NSArray + try assertEquals(actual: list.object(at: 2) as! NSNumber, expected: NSNumber(value: 2)) + try assertEquals(actual: list.object(at: 1) as! NSNull, expected: NSNull()) + try assertEquals(actual: list.count, expected: 5) + } + + func testMutableList() throws { + let kotlinList = Stdlib.emptyMutableList() as! NSMutableArray + let nsList = NSMutableArray() + + func apply(op: (NSMutableArray)->T) throws { + let actual = op(kotlinList) + let expected = op(nsList) + try assertEquals(actual: actual, expected: expected) + try assertEquals(actual: kotlinList, expected: nsList) + try assertEquals(actual: kotlinList.hash, expected: nsList.hash) + } + + func applyVoid(op: (NSMutableArray)->Void) throws { + op(kotlinList) + op(nsList) + try assertEquals(actual: kotlinList, expected: nsList) + try assertEquals(actual: kotlinList.hash, expected: nsList.hash) + } + + try apply { $0.count } + try applyVoid { $0.insert(0, at: 0) } + try applyVoid { $0.insert(1, at: 0) } + try applyVoid { $0.insert(2, at: 1) } + try applyVoid { $0.removeObject(at: 0) } + try applyVoid { $0.add("foo") } + try applyVoid { $0.removeLastObject() } + try applyVoid { $0.replaceObject(at: 0, with: "bar") } + let NULL: Any? = nil + try applyVoid { $0.add(NULL as Any) } + try applyVoid { $0.insert(NULL as Any, at: 2) } + try applyVoid { $0.replaceObject(at: 1, with: NULL as Any) } + try apply { $0.count } + } + + func testMutableSet() throws { + let kotlinSet = Stdlib.emptyMutableSet() as! NSMutableSet + let nsSet = NSMutableSet() + + func apply(op: (NSMutableSet)->T) throws { + let actual = op(kotlinSet) + let expected = op(nsSet) + try assertEquals(actual: actual, expected: expected) + try assertEquals(actual: kotlinSet, expected: nsSet) + try assertEquals(actual: kotlinSet.hash, expected: nsSet.hash) + } + + func applyVoid(op: (NSMutableSet)->Void) throws { + op(kotlinSet) + op(nsSet) + try assertEquals(actual: kotlinSet, expected: nsSet) + try assertEquals(actual: kotlinSet.hash, expected: nsSet.hash) + } + + try apply { $0.count } + try applyVoid { $0.add("foo") } + try applyVoid { $0.add("bar") } + try applyVoid { $0.remove("baz") } + try applyVoid { $0.add("baz") } + try applyVoid { $0.add(StdlibTripleVals(first: 1, second: 2, third: 3)) } + try apply { $0.member(StdlibTripleVals(first: 1, second: 2, third: 3)) as! StdlibTripleVals } + try apply { $0.member(42) == nil } + try applyVoid { $0.remove(StdlibTripleVals(first: 1, second: 2, third: 3)) } + + let NULL0: Any? = nil + let NULL = NULL0 as Any + + try applyVoid { $0.add(NULL) } + try apply { $0.member(NULL) == nil } + try apply { $0.member(NULL) as! NSObject } + try applyVoid { $0.remove(NULL) } + try apply { $0.member(NULL) == nil } + + try apply { NSSet(array: $0.objectEnumerator().remainingObjects()) } + + try apply { $0.count } + } + + func testMutableMap() throws { + // TODO: test KotlinMutableSet/Dictionary constructors + let kotlinMap = Stdlib.emptyMutableMap() as! NSMutableDictionary + let nsMap = NSMutableDictionary() + + func apply(op: (NSMutableDictionary)->T) throws { + let actual = op(kotlinMap) + let expected = op(nsMap) + try assertEquals(actual: actual, expected: expected) + try assertEquals(actual: kotlinMap, expected: nsMap) + try assertEquals(actual: kotlinMap.hash, expected: nsMap.hash) + } + + func applyVoid(op: (NSMutableDictionary) throws -> Void) throws { + try op(kotlinMap) + try op(nsMap) + try assertEquals(actual: kotlinMap, expected: nsMap) + try assertEquals(actual: kotlinMap.hash, expected: nsMap.hash) + } + + try apply { $0.count } + try apply { $0.object(forKey: 42) == nil } + try applyVoid { $0.setObject(42, forKey: 42 as NSNumber) } + try applyVoid { $0.setObject(17, forKey: "foo" as NSString) } + let triple = StdlibTripleVals(first: 3, second: 2, third: 1) + try applyVoid { $0.setObject("bar", forKey: triple) } + try applyVoid { $0.removeObject(forKey: 42) } + try apply { $0.count } + try apply { $0.object(forKey: 42) == nil } + try apply { $0.object(forKey: "foo") as! NSObject } + try apply { $0.object(forKey: triple) as! NSObject } + + try apply { NSSet(array: $0.keyEnumerator().remainingObjects()) } + + let NULL0: Any? = nil + let NULL = NULL0 as Any + + try apply { $0.object(forKey: NULL) == nil } + + + try applyVoid { $0.setObject(42, forKey: NULL as! NSCopying) } + try applyVoid { $0.setObject(NULL, forKey: "baz" as NSString) } + try apply { $0.object(forKey: NULL) as! NSObject } + try apply { $0.object(forKey: "baz") as! NSObject } + + try apply { NSSet(array: $0.keyEnumerator().remainingObjects()) } + + try applyVoid { $0.removeObject(forKey: NULL) } + try applyVoid { $0.removeObject(forKey: "baz") } + + try applyVoid { $0.removeAllObjects() } + + let myKey = MyKey() + try applyVoid { $0.setObject(myKey, forKey: myKey) } + try applyVoid { + let key = $0.allKeys[0] as! MyKey + let value = $0.allValues[0] as! MyKey + try assertFalse(key === myKey) + try assertTrue(value === myKey) + } + + } + + @objc class MyKey : NSObject, NSCopying { + override var hash: Int { + return 42 + } + + override func isEqual(_ object: Any?) -> Bool { + return object is MyKey + } + + func copy(with: NSZone? = nil) -> Any { + return MyKey() + } + + } + + func testSet() throws { + let elements = StdlibStdlibArray(size: 2) { index in nil } + elements.set(index: 0, value: nil) + elements.set(index: 1, value: 42) + let set = Stdlib.set(elements: elements) as! NSSet + try assertEquals(actual: set.count, expected: 2) + try assertEquals(actual: set.member(NSNull()) as! NSNull, expected: NSNull()) + try assertEquals(actual: set.member(42) as! NSNumber, expected: NSNumber(value: 42 as Int32)) + try assertTrue(set.member(17) == nil) + try assertFalse(set.member(42) as AnyObject === NSNumber(value: 42 as Int32)) + try assertTrue(set.contains(42)) + try assertTrue(set.contains(nil as Any?)) + try assertFalse(set.contains(17)) + + try assertEquals(actual: NSSet(array: set.objectEnumerator().remainingObjects()), expected: NSSet(array: [nil, 42] as [AnyObject])) + } + + func testMap() throws { + let elements = StdlibStdlibArray(size: 6) { index in nil } + elements.set(index: 0, value: nil) + elements.set(index: 1, value: 42) + elements.set(index: 2, value: "foo") + elements.set(index: 3, value: "bar") + elements.set(index: 4, value: 42) + elements.set(index: 5, value: nil) + + let map = Stdlib.map(keysAndValues: elements) as! NSDictionary + try assertEquals(actual: map.count, expected: 3) + + try assertEquals(actual: map.object(forKey: NSNull()) as! NSNumber, expected: NSNumber(value: 42)) + try assertEquals(actual: map.object(forKey: "foo") as! String, expected: "bar") + try assertEquals(actual: map.object(forKey: 42) as! NSNull, expected: NSNull()) + try assertTrue(map.object(forKey: "bar") == nil) + + try assertEquals(actual: NSSet(array: map.keyEnumerator().remainingObjects()), expected: NSSet(array: [nil, 42, "foo"] as [AnyObject])) + } + +} diff --git a/backend.native/tests/framework/values/values.kt b/backend.native/tests/framework/values/values.kt new file mode 100644 index 00000000000..1428dad11aa --- /dev/null +++ b/backend.native/tests/framework/values/values.kt @@ -0,0 +1,195 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * 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. + */ + +// All classes and methods should be used in tests +@file:Suppress("UNUSED") + +package conversions + +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +// Constants +const val dbl: Double = 3.14 +const val flt: Float = 2.73F +const val integer: Int = 42 +const val longInt: Long = 1984 + +// Vars +var intVar: Int = 451 +var str = "Kotlin String" +var strAsAny: Any = "Kotlin String as Any" + +// MIN/MAX values as Numbers +var minDoubleVal: kotlin.Number = Double.MIN_VALUE +var maxDoubleVal: kotlin.Number = Double.MAX_VALUE + +// Infinities and NaN +val nanDoubleVal: Double = Double.NaN +val nanFloatVal: Float = Float.NaN +val infDoubleVal: Double = Double.POSITIVE_INFINITY +val infFloatVal: Float = Float.NEGATIVE_INFINITY + +// Boolean +val boolVal: Boolean = true +val boolAnyVal: Any = false + +// Lists +val numbersList: List = listOf(1.toByte(), 2.toShort(), 13) +val anyList: List = listOf("Str", 42, 3.14, true) + +// lateinit +lateinit var lateinitIntVar: Any + +// lazy +val lazyVal: String by lazy { + println("Lazy value initialization") + "Lazily initialized string" +} + +// Delegation +var delegatedGlobalArray: Array by DelegateClass() + +class DelegateClass: ReadWriteProperty> { + + private var holder: Array = arrayOf("property") + + override fun getValue(thisRef: Nothing?, property: KProperty<*>): Array { + return arrayOf("Delegated", "global", "array") + holder + } + + override fun setValue(thisRef: Nothing?, property: KProperty<*>, value: Array) { + holder = value + } +} + +// Getter with delegation +val delegatedList: List + get() = delegatedGlobalArray.toList() + +// Null +val nullVal: Any? = null +var nullVar: String? = "" + +// Any +var anyValue: Any = "Str" + +// Functions +fun emptyFun() { } + +fun strFun(): String = "fooStr" + +fun argsFun(i: Int, l: Long, d: Double, s: String): Any = s + i + l + d + +fun funArgument(foo: () -> String): String = foo() + +// Generic functions +fun genericFoo(t: T, foo: (T) -> R): R = foo(t) + +fun fooGenericNumber(r: R, foo: (T) -> Number): Number = foo(r) + +fun varargToList(vararg args: T): List = args.toList() + +// Extensions +fun String.subExt(i: Int): String { + return if (i < this.length) this[i].toString() else "nothing" +} + +fun Any?.toString(): String = this?.toString() ?: "null" + +fun Any?.print() = println(this.toString()) + +// Lambdas +val sumLambda = { x: Int, y: Int -> x + y } + + +// Inheritance +interface I { + fun iFun(): String = "I::iFun" +} + +private interface PI { + fun piFun(): Any + fun iFun(): String = "PI::iFun" +} + +class DefaultInterfaceExt : I + +open class OpenClassI : I { + override fun iFun(): String = "OpenClassI::iFun" +} + +class FinalClassExtOpen : OpenClassI() { + override fun iFun(): String = "FinalClassExtOpen::iFun" +} + +open class MultiExtClass : OpenClassI(), PI { + override fun piFun(): Any { + return 42 + } + + override fun iFun(): String = super.iFun() +} + +open class ConstrClass(open val i: Int, val s: String, val a: Any = "AnyS") : OpenClassI() + +class ExtConstrClass(override val i: Int) : ConstrClass(i, "String") { + override fun iFun(): String = "ExtConstrClass::iFun::$i-$s-$a" +} + +// Enum +enum class Enumeration(val enumValue: Int) { + ANSWER(42), YEAR(1984), TEMPERATURE(451) +} + +fun passEnum(): Enumeration { + return Enumeration.ANSWER +} + +fun receiveEnum(e: Int) { + println("ENUM got: ${get(e).enumValue}") +} + +fun get(value: Int): Enumeration { + return Enumeration.values()[value] +} + +// Data class values and generated properties: component# and toString() +data class TripleVals(val first: T, val second: T, val third: T) + +data class TripleVars(var first: T, var second: T, var third: T) { + override fun toString(): String { + return "[$first, $second, $third]" + } +} + +open class WithCompanionAndObject { + companion object { + val str = "String" + var named: I? = Named + } + + object Named : OpenClassI() { + override fun iFun(): String = "WithCompanionAndObject.Named::iFun" + } +} + +fun getCompanionObject() = WithCompanionAndObject.Companion +fun getNamedObject() = WithCompanionAndObject.Named +fun getNamedObjectInterface(): OpenClassI = WithCompanionAndObject.Named + +typealias EE = Enumeration +fun EE.getAnswer() : EE = Enumeration.ANSWER diff --git a/backend.native/tests/framework/values/values.swift b/backend.native/tests/framework/values/values.swift new file mode 100644 index 00000000000..e3d366afcc3 --- /dev/null +++ b/backend.native/tests/framework/values/values.swift @@ -0,0 +1,312 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * 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. + */ + +import Foundation +import Values + +// -------- Tests -------- + +func testVals() throws { + print("Values from Swift") + let dbl = Values.dbl + let flt = Values.flt + let int = Values.integer + let long = Values.longInt + + print(dbl) + print(flt) + print(int) + print(long) + + try assertEquals(actual: dbl, expected: 3.14 as Double, "Double value isn't equal.") + try assertEquals(actual: flt, expected: 2.73 as Float, "Float value isn't equal.") + try assertEquals(actual: int, expected: 42) + try assertEquals(actual: long, expected: 1984) +} + +func testVars() throws { + print("Variables from Swift") + var intVar = Values.intVar + var strVar = Values.str + var strAsId = Values.strAsAny + + print(intVar) + print(strVar) + print(strAsId) + + try assertEquals(actual: intVar, expected: 451) + try assertEquals(actual: strVar, expected: "Kotlin String") + try assertEquals(actual: strAsId as! String, expected: "Kotlin String as Any") + + strAsId = "Swift str" + Values.strAsAny = strAsId + print(Values.strAsAny) + try assertEquals(actual: Values.strAsAny as! String, expected: strAsId as! String) + + // property with custom getter/setter backed by the Kotlin's var + var intProp : Int32 { + get { + return Values.intVar * 2 + } + set(value) { + Values.intVar = 123 + value + } + } + intProp += 10 + print(intProp) + print(Values.intVar) + try assertEquals(actual: Values.intVar * 2, expected: intProp, "Property backed by var") +} + +func testDoubles() throws { + print("Doubles in Swift") + let minDouble = Values.minDoubleVal as! Double + let maxDouble = Values.maxDoubleVal as! NSNumber + + print(minDouble) + print(maxDouble) + print(Values.nanDoubleVal) + print(Values.nanFloatVal) + print(Values.infDoubleVal) + print(Values.infFloatVal) + + try assertEquals(actual: minDouble, expected: Double.leastNonzeroMagnitude, "Min double") + try assertEquals(actual: maxDouble, expected: Double.greatestFiniteMagnitude as NSNumber, "Max double") + try assertTrue(Values.nanDoubleVal.isNaN, "NaN double") + try assertTrue(Values.nanFloatVal.isNaN, "NaN float") + try assertEquals(actual: Values.infDoubleVal, expected: Double.infinity, "Inf double") + try assertEquals(actual: Values.infFloatVal, expected: -Float.infinity, "-Inf float") +} + +func testLists() throws { + let numbersList = Values.numbersList + let gold = [1, 2, 13] + for i in 0.. String = { return s } + try assertEquals(actual: Values.funArgument(foo: fFunc), expected: s, "Using function type arguments failed") +} + +func testGenericsFoo() throws { + let fun = { (i: Int) -> String in return "S \(i)" } + // wrap lambda to workaround issue with type conversion inability: + // (Int) -> String can't be cast to (Any?) -> Any? + let wrapper = { (t: Any?) -> Any? in return fun(t as! Int) } + let res = Values.genericFoo(t: 42, foo: wrapper) + try assertEquals(actual: res as! String, expected: "S 42") +} + +func testVararg() throws { + let ktArray = ValuesStdlibArray(size: 3, init: { (_) -> Int in return 42 }) + let arr: [Int] = Values.varargToList(args: ktArray) as! [Int] + try assertEquals(actual: arr, expected: [42, 42, 42]) +} + +func testStrExtFun() throws { + try assertEquals(actual: Values.subExt("String", i: 2), expected: "r") + try assertEquals(actual: Values.subExt("A", i: 2), expected: "nothing") +} + +func testAnyToString() throws { + try assertEquals(actual: Values.toString(nil), expected: "null") + try assertEquals(actual: Values.toString(42), expected: "42") +} + +func testAnyPrint() throws { + print("BEGIN") + Values.print(nil) + Values.print("Print") + Values.print(123456789) + Values.print(3.14) + Values.print([3, 2, 1]) + print("END") +} + +func testLambda() throws { + try assertEquals(actual: Values.sumLambda(3, 4), expected: 7) +} + +// -------- Tests for classes and interfaces ------- +class ValIEmptyExt : ValuesI { + func iFun() -> String { + return "ValIEmptyExt::iFun" + } +} + +class ValIExt : ValuesI { + func iFun() -> String { + return "ValIExt::iFun" + } +} + +func testInterfaceExtension() throws { + try assertEquals(actual: ValIEmptyExt().iFun(), expected: "ValIEmptyExt::iFun") + try assertEquals(actual: ValIExt().iFun(), expected: "ValIExt::iFun") +} + +func testClassInstances() throws { + try assertEquals(actual: ValuesOpenClassI().iFun(), expected: "OpenClassI::iFun") + try assertEquals(actual: ValuesDefaultInterfaceExt().iFun(), expected: "I::iFun") + try assertEquals(actual: ValuesFinalClassExtOpen().iFun(), expected: "FinalClassExtOpen::iFun") + try assertEquals(actual: ValuesMultiExtClass().iFun(), expected: "PI::iFun") + try assertEquals(actual: ValuesMultiExtClass().piFun() as! Int, expected: 42) + try assertEquals(actual: ValuesConstrClass(i: 1, s: "str", a: "Any").iFun(), expected: "OpenClassI::iFun") + try assertEquals(actual: ValuesExtConstrClass(i: 123).iFun(), expected: "ExtConstrClass::iFun::123-String-AnyS") +} + +func testEnum() throws { + try assertEquals(actual: Values.passEnum().enumValue, expected: 42) + try assertEquals(actual: Values.passEnum().name, expected: "ANSWER") + Values.receiveEnum(e: 1) +} + +func testDataClass() throws { + let f = "1" + let s = "2" + let t = "3" + + let tripleVal = ValuesTripleVals(first: f, second: s, third: t) + try assertEquals(actual: tripleVal.first as! String, expected: f, "Data class' value") + try assertEquals(actual: tripleVal.component2() as! String, expected: s, "Data class' component") + print(tripleVal) + try assertEquals(actual: String(describing: tripleVal), expected: "TripleVals(first=\(f), second=\(s), third=\(t))") + + let tripleVar = ValuesTripleVars(first: f, second: s, third: t) + try assertEquals(actual: tripleVar.first as! String, expected: f, "Data class' value") + try assertEquals(actual: tripleVar.component2() as! String, expected: s, "Data class' component") + print(tripleVar) + try assertEquals(actual: String(describing: tripleVar), expected: "[\(f), \(s), \(t)]") + + tripleVar.first = t + tripleVar.second = f + tripleVar.third = s + try assertEquals(actual: tripleVar.component2() as! String, expected: f, "Data class' component") + try assertEquals(actual: String(describing: tripleVar), expected: "[\(t), \(f), \(s)]") +} + +func testCompanionObj() throws { + try assertEquals(actual: ValuesWithCompanionAndObjectCompanion().str, expected: "String") + try assertEquals(actual: Values.getCompanionObject().str, expected: "String") + + let namedFromCompanion = Values.getCompanionObject().named + let named = Values.getNamedObject() + try assertTrue(named === namedFromCompanion, "Should be the same Named object") + + try assertEquals(actual: Values.getNamedObjectInterface().iFun(), expected: named.iFun(), "Named object's method") +} + +// -------- Execution of the test -------- + +class ValuesTests : TestProvider { + var tests: [TestCase] = [] + + init() { + providers.append(self) + tests = [ + TestCase(name: "TestValues", method: withAutorelease(testVals)), + TestCase(name: "TestVars", method: withAutorelease(testVars)), + TestCase(name: "TestDoubles", method: withAutorelease(testDoubles)), + TestCase(name: "TestLists", method: withAutorelease(testLists)), + TestCase(name: "TestLazyValues", method: withAutorelease(testLazyVal)), + TestCase(name: "TestDelegatedProperties", method: withAutorelease(testDelegatedProp)), + TestCase(name: "TestGetterDelegate", method: withAutorelease(testGetterDelegate)), + TestCase(name: "TestNulls", method: withAutorelease(testNulls)), + TestCase(name: "TestAnyVar", method: withAutorelease(testAnyVar)), + TestCase(name: "TestFunctions", method: withAutorelease(testFunctions)), + TestCase(name: "TestFuncType", method: withAutorelease(testFuncType)), + TestCase(name: "TestGenericsFoo", method: withAutorelease(testGenericsFoo)), + TestCase(name: "TestVararg", method: withAutorelease(testVararg)), + TestCase(name: "TestStringExtension", method: withAutorelease(testStrExtFun)), + TestCase(name: "TestAnyToString", method: withAutorelease(testAnyToString)), + TestCase(name: "TestAnyPrint", method: withAutorelease(testAnyPrint)), + TestCase(name: "TestLambda", method: withAutorelease(testLambda)), + TestCase(name: "TestInterfaceExtension", method: withAutorelease(testInterfaceExtension)), + TestCase(name: "TestClassInstances", method: withAutorelease(testClassInstances)), + TestCase(name: "TestEnum", method: withAutorelease(testEnum)), + TestCase(name: "TestDataClass", method: withAutorelease(testDataClass)), + TestCase(name: "TestCompanionObj", method: withAutorelease(testCompanionObj)), + ] + } +} diff --git a/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt b/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt index 2972fb89a7f..4b437d202ff 100644 --- a/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt +++ b/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt @@ -42,6 +42,9 @@ interface ExecutorService { fun execute(action: Action): ExecResult? } +/** + * Creates an ExecutorService depending on a test target -Ptest_target + */ fun create(project: Project): ExecutorService { val platformManager = project.rootProject.findProperty("platformManager") as PlatformManager val testTarget = platformManager.targetManager(project.findProperty("testTarget") as String?).target @@ -87,6 +90,39 @@ fun create(project: Project): ExecutorService { } } +data class ProcessOutput(val stdOut: String, val stdErr: String, val exitCode: Int) + +/** + * Runs process using a given executor. + * + * @param executor a method that is able to run a given executable, e.g. ExecutorService::execute + * @param executable a process executable to be run + * @param args arguments for a process + */ +fun runProcess(executor: (Action) -> ExecResult?, + executable: String, args: List) : ProcessOutput { + val outStream = ByteArrayOutputStream() + val errStream = ByteArrayOutputStream() + + val execResult = executor(Action { + it.executable = executable + it.args = args.toList() + it.standardOutput = outStream + it.errorOutput = errStream + it.isIgnoreExitValue = true + }) + + checkNotNull(execResult) + + val stdOut = outStream.toString("UTF-8") + val stdErr = errStream.toString("UTF-8") + + return ProcessOutput(stdOut, stdErr, execResult!!.exitValue) +} + +fun runProcess(executor: (Action) -> ExecResult?, + executable: String, vararg args: String) = runProcess(executor, executable, args.toList()) + /** * Executes a given action with iPhone Simulator. * diff --git a/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt b/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt new file mode 100644 index 00000000000..d463cbde195 --- /dev/null +++ b/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt @@ -0,0 +1,126 @@ +package org.jetbrains.kotlin + +import org.gradle.api.Action +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.TaskAction +import org.gradle.process.ExecSpec + +import org.jetbrains.kotlin.konan.target.* + +import java.io.FileWriter +import java.nio.file.Path +import java.nio.file.Paths + +/** + * Test task for -produce framework testing. Requires a framework to be built by the Konan plugin + * with konanArtifacts { framework(frameworkName, targets: [ testTarget] ) } and a dependency set + * according to a pattern "compileKonan${frameworkName}". + * + * @property swiftSources Swift-language test sources that use a given framework + * @property frameworkName a framework name + */ +open class FrameworkTest : DefaultTask() { + @Input + lateinit var swiftSources: List + + @Input + lateinit var frameworkName: String + + private val testOutput by lazy { + ((project.findProperty("sourceSets") as SourceSetContainer) + .getByName("testOutputFramework") as SourceSet).output.dirs.singleFile.absolutePath + ?: throw RuntimeException("Empty sourceSet") + } + + @TaskAction + fun run() { + val frameworkPath = "$testOutput/$frameworkName/${project.testTarget().name}" + + // Sign framework + val (stdOut, stdErr, exitCode) = runProcess(executor = localExecutor, executable = "/usr/bin/codesign", + args = listOf("--verbose", "-s", "-", Paths.get(frameworkPath, "$frameworkName.framework").toString())) + check(exitCode == 0, { """ + |Codesign failed with exitCode: $exitCode + |stdout: $stdOut + |stderr: $stdErr + """.trimMargin() + }) + + // create a test provider and get main entry point + val provider = Paths.get(testOutput, frameworkName, "provider.swift") + FileWriter(provider.toFile()).use { + it.write(""" + |// THIS IS AUTOGENERATED FILE + |// This method is invoked by the main routine to get a list of tests + |func registerProvider() { + | // TODO: assuming this naming for now + | ${frameworkName}Tests() + |} + """.trimMargin()) + } + val testHome = project.file("framework").toPath() + val swiftMain = Paths.get(testHome.toString(), "main.swift").toString() + + // Compile swift sources + val sources = swiftSources.map { Paths.get(it).toString() } + + listOf(provider.toString(), swiftMain) + val options = listOf("-g", "-Xlinker", "-rpath", "-Xlinker", frameworkPath, "-F", frameworkPath) + val testExecutable = Paths.get(testOutput, frameworkName, "swiftTestExecutable") + swiftc(sources, options, testExecutable) + + runTest(testExecutable) + } + + private fun runTest(testExecutable: Path) { + val executor = (project.convention.plugins["executor"] as? ExecutorService) + ?: throw RuntimeException("Executor wasn't found") + val (stdOut, stdErr, exitCode) = runProcess(executor = executor::execute, + executable = testExecutable.toString()) + + println(""" + |stdout: $stdOut + |stderr: $stdErr + """.trimMargin()) + check(exitCode == 0, { "Execution failed with exit code: $exitCode "}) + } + + private val localExecutor = { a: Action -> project.exec(a) } + + private fun Project.platformManager() = rootProject.findProperty("platformManager") as PlatformManager + + private fun Project.testTarget() = platformManager() + .targetManager(project.findProperty("testTarget") as String?).target + + private fun swiftc(sources: List, options: List, output: Path) { + val target = project.testTarget() + val platform = project.platformManager().platform(target) + assert(platform.configurables is AppleConfigurables) + val configs = platform.configurables as AppleConfigurables + val compiler = configs.absoluteTargetToolchain + "/usr/bin/swiftc" + + val swiftTarget = when (target) { + KonanTarget.IOS_X64 -> "x86_64-apple-ios" + configs.osVersionMin + KonanTarget.IOS_ARM64 -> "arm64_64-apple-ios" + configs.osVersionMin + KonanTarget.MACOS_X64 -> "x86_64-apple-macosx" + configs.osVersionMin + else -> throw IllegalStateException("Test target $target is not supported") + } + + val args = listOf("-sdk", configs.absoluteTargetSysRoot, "-target", swiftTarget) + + options + "-o" + output.toString() + sources + + val (stdOut, stdErr, exitCode) = runProcess(executor = localExecutor, executable = compiler, args = args) + + println(""" + |$compiler finished with exit code: $exitCode + |options: ${args.joinToString(separator = " ")} + |stdout: $stdOut + |stderr: $stdErr + """.trimMargin()) + check(exitCode == 0, { "Compilation failed" }) + check(output.toFile().exists(), { "Compiler swiftc hasn't produced an output file: $output" }) + } +}