diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e7341d..57c4e7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. --- ## master +* Introduced Ksp code generation using the @Mock annotation * Fix for mock generation to set the right visibility ## 2.6.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180..41d9927 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fc..ae04661 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/mockingbird-processor/build.gradle.kts b/mockingbird-processor/build.gradle.kts new file mode 100644 index 0000000..d2e758f --- /dev/null +++ b/mockingbird-processor/build.gradle.kts @@ -0,0 +1,49 @@ +/** + * + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + + +plugins { + kotlin("jvm") + id("maven-publish") +} + +apply(from = "../publishing.gradle") + +repositories { + mavenCentral() + google() + gradlePluginPortal() +} + +tasks.withType().configureEach { + kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" +} + +publishing { + publications { + create("mockingbird-processor") { + from(components["java"]) + } + } +} + +dependencies { + implementation(libs.google.ksp) + implementation(libs.square.kotlinpoet.ksp) + implementation(project(":mockingbird")) +} + diff --git a/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/FunctionsMiner.kt b/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/FunctionsMiner.kt new file mode 100644 index 0000000..10f8d82 --- /dev/null +++ b/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/FunctionsMiner.kt @@ -0,0 +1,56 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.processor + +import com.google.devtools.ksp.getAllSuperTypes +import com.google.devtools.ksp.getDeclaredFunctions +import com.google.devtools.ksp.getDeclaredProperties +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.google.devtools.ksp.symbol.Modifier + +class FunctionsMiner { + + /** + * Extract all functions and properties, this will extract also functions that are defined into the supertypes + * these functions are all the functions that the mock class should provide + */ + fun extractFunctionsAndProperties(kmClass: KSClassDeclaration): Pair, List> { + val functions: MutableList = mutableListOf() + val properties: MutableList = mutableListOf() + + val kmSuperTypes = kmClass.getAllSuperTypes() + .filter { it.fullyQualifiedName() != "kotlin.Any" } + .map { it.declaration } + .filter { it is KSClassDeclaration } + .map { it as KSClassDeclaration } + + (listOf(kmClass) + kmSuperTypes).forEach { rawExtractFunctionsAndProperties(it, functions, properties) } + return functions to properties + } + + private fun rawExtractFunctionsAndProperties( + kmClass: KSClassDeclaration, + functions: MutableList, + properties: MutableList + ) { + // get functions and properties for current class + functions.addAll(kmClass.getDeclaredFunctions().filterNot { it.modifiers.contains(Modifier.OVERRIDE) }) + properties.addAll(kmClass.getDeclaredProperties().filterNot { it.modifiers.contains(Modifier.OVERRIDE) }) + } +} \ No newline at end of file diff --git a/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/GenerateMocksSymbolProcessor.kt b/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/GenerateMocksSymbolProcessor.kt new file mode 100644 index 0000000..0f6a363 --- /dev/null +++ b/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/GenerateMocksSymbolProcessor.kt @@ -0,0 +1,61 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.processor + +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.google.devtools.ksp.symbol.KSVisitorVoid +import com.google.devtools.ksp.validate +import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview +import com.squareup.kotlinpoet.ksp.toClassName +import com.squareup.kotlinpoet.ksp.toTypeName +import com.squareup.kotlinpoet.ksp.writeTo + +class GenerateMocksSymbolProcessor( + val codeGenerator: CodeGenerator, + val logger: KSPLogger +) : SymbolProcessor { + + private lateinit var mockGenerator: MockGenerator + private lateinit var functionsMiner: FunctionsMiner + + @OptIn(KotlinPoetKspPreview::class) + override fun process(resolver: Resolver): List { + functionsMiner = FunctionsMiner() + mockGenerator = MockGenerator(resolver, logger, functionsMiner) + + val fields = resolver.getSymbolsWithAnnotation(MOCK_ANNOTATION) + fields.forEach { set -> + if (set !is KSPropertyDeclaration) error("$set is not a property declaration but is annotated with @Mock, not supported") + mockGenerator.createClass(set.type).writeTo( + codeGenerator = codeGenerator, + aggregating = false + ) + } + return emptyList() + } + + companion object { + const val MOCK_ANNOTATION = "com.careem.mockingbird.test.annotations.Mock" + } +} \ No newline at end of file diff --git a/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/GenerateMocksSymbolProcessorProvider.kt b/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/GenerateMocksSymbolProcessorProvider.kt new file mode 100644 index 0000000..5962580 --- /dev/null +++ b/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/GenerateMocksSymbolProcessorProvider.kt @@ -0,0 +1,27 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.processor + +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +class GenerateMocksSymbolProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + return GenerateMocksSymbolProcessor(environment.codeGenerator, environment.logger) + } +} \ No newline at end of file diff --git a/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/KSTypeExt.kt b/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/KSTypeExt.kt new file mode 100644 index 0000000..1a6b0b4 --- /dev/null +++ b/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/KSTypeExt.kt @@ -0,0 +1,6 @@ +package com.careem.mockingbird.processor + +import com.google.devtools.ksp.symbol.KSType + +internal fun KSType.fullyQualifiedName() = + "${this.declaration.qualifiedName?.asString()}" diff --git a/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/MockGenerator.kt b/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/MockGenerator.kt new file mode 100644 index 0000000..9a13440 --- /dev/null +++ b/mockingbird-processor/src/main/kotlin/com/careem/mockingbird/processor/MockGenerator.kt @@ -0,0 +1,354 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.processor + +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.google.devtools.ksp.symbol.KSType +import com.google.devtools.ksp.symbol.KSTypeReference +import com.google.devtools.ksp.symbol.Modifier +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.MemberName +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.buildCodeBlock +import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview +import com.squareup.kotlinpoet.ksp.toClassName +import com.squareup.kotlinpoet.ksp.toKModifier +import com.squareup.kotlinpoet.ksp.toTypeName + +class MockGenerator constructor( + private val resolver: Resolver, + private val logger: KSPLogger, + private val functionsMiner: FunctionsMiner +) { + + @OptIn(KotlinPoetKspPreview::class) + fun createClass(ksTypeRef: KSTypeReference): FileSpec { + + val classToMock = ksTypeRef.resolve() + val className = classToMock.toClassName() + val simpleName = className.simpleName + + val ksClassDeclaration = classToMock.declaration as KSClassDeclaration + + val packageName = className.packageName + val externalClass = + resolver.getClassDeclarationByName(resolver.getKSNameFromString("com.careem.mockingbird.test.Mock")) + + logger.info("Generating mocks for $simpleName") + + val (functionsToMock, propertiesToMock) = functionsMiner.extractFunctionsAndProperties(ksClassDeclaration) + + val mockClassBuilder = TypeSpec.classBuilder("${simpleName}Mock") + .addType(functionsToMock.buildMethodObject()) + .addType(functionsToMock.buildArgObject()) + .addType(propertiesToMock.buildPropertyObject()) + .addSuperinterface(classToMock.toTypeName()) + .addSuperinterface(externalClass!!.toClassName()) + + ksClassDeclaration.modifiers.filter { + it == Modifier.PUBLIC || it == Modifier.INTERNAL || it == Modifier.PROTECTED || it == Modifier.PRIVATE + }.forEach { modifier -> + modifier.toKModifier()?.let { mockClassBuilder.addModifiers(it) } + } + + functionsToMock.forEach { function -> + mockFunction(mockClassBuilder, function, isUnitFunction(function)) + } + + val uuid = PropertySpec.builder("uuid", String::class, KModifier.OVERRIDE) + .addModifiers(KModifier.PUBLIC) + .delegate( + buildCodeBlock { + val uuid = MemberName("com.careem.mockingbird.test", "uuid") + add("%M()", uuid) + }) + .build() + + mockClassBuilder.addProperty(uuid) + + propertiesToMock.forEach { property -> + mockProperty(mockClassBuilder, property) + } + + return FileSpec.builder(packageName, "${simpleName}Mock") + .addType(mockClassBuilder.build()) + .build() + } + + private fun List.buildMethodObject(): TypeSpec { + logger.info("Generating methods") + val methodObjectBuilder = TypeSpec.objectBuilder(METHOD) + val visitedFunctionSet = mutableSetOf() + for (function in this) { + val functionName = function.simpleName.getShortName() + if (!visitedFunctionSet.contains(functionName)) { + visitedFunctionSet.add(functionName) + methodObjectBuilder.addProperty( + PropertySpec.builder(functionName, String::class) + .initializer("%S", functionName) + .addModifiers(KModifier.CONST) + .build() + ) + } + } + return methodObjectBuilder.build() + } + + private fun List.buildArgObject(): TypeSpec { + logger.info("Generating arguments") + val argObjectBuilder = TypeSpec.objectBuilder(ARG) + val visitedPropertySet = mutableSetOf() + for (function in this) { + for (arg in function.parameters) { + val argName = arg.name!!.getShortName() + logger.info("Argument: $argName") + if (!visitedPropertySet.contains(argName)) { + visitedPropertySet.add(argName) + argObjectBuilder.addProperty( + PropertySpec.builder(argName, String::class) + .initializer("%S", argName) + .addModifiers(KModifier.CONST) + .build() + ) + } + } + + } + return argObjectBuilder.build() + } + + private fun List.buildPropertyObject(): TypeSpec { + logger.info("Generating properties") + val propertyObjectBuilder = TypeSpec.objectBuilder(PROPERTY) + var haveMutableProps = false + val visitedPropertySet = mutableSetOf() + this.forEach { property -> + logger.info("Property: $property") + val rawName = property.simpleName.getShortName() + property.getter?.let { + handleProperty(adjustPropertyName(true, rawName), visitedPropertySet, propertyObjectBuilder) + } + + property.setter?.let { + haveMutableProps = true + handleProperty(adjustPropertyName(false, rawName), visitedPropertySet, propertyObjectBuilder) + } + } + + if (haveMutableProps) { + val setterValueProperty = buildProperty(PROPERTY_SETTER_VALUE) + propertyObjectBuilder.addProperty(setterValueProperty) + } + return propertyObjectBuilder.build() + } + + private fun adjustPropertyName(isGetter: Boolean, rawName: String): String { + val prefix = if (isGetter) { + if (rawName.startsWith("is", ignoreCase = true)) "" + else "get" + } else { + "set" + } + val newName = if (prefix.isNotEmpty()) rawName.replaceFirstChar(Char::titlecase) else rawName + return "$prefix$newName" + } + + private fun handleProperty( + name: String, + visited: MutableSet, + builder: TypeSpec.Builder + ) { + if (!visited.contains(name)) { + visited.add(name) + val nameProperty = buildProperty(name) + builder.addProperty(nameProperty) + } + } + + private fun buildProperty(name: String) = + PropertySpec.builder(name, String::class) + .initializer("%S", name) + .addModifiers(KModifier.CONST) + .build() + + private fun isUnitFunction(function: KSFunctionDeclaration): Boolean { + val classifier = function.returnType + val ksType = classifier!!.resolve() + return ksType.fullyQualifiedName() == "kotlin.Unit" + } + + @OptIn(KotlinPoetKspPreview::class) + private fun mockProperty( + mockClassBuilder: TypeSpec.Builder, + property: KSPropertyDeclaration + ) { + logger.info("===> Mocking Property ${property.getter} and ${property.setter}") + val propertyBuilder = PropertySpec + .builder( + property.simpleName.getShortName(), + property.type.resolve().toTypeName(), + KModifier.OVERRIDE + ) + + if (property.getter != null) { + val getterBuilder = FunSpec.getterBuilder() + val mockFunction = MemberName("com.careem.mockingbird.test", MOCK) + val getterArgsValue = mutableListOf( + mockFunction, + MemberName( + "", + adjustPropertyName(true, property.simpleName.getShortName()) + ) + ) + val getterCodeBlocks = mutableListOf("methodName = ${PROPERTY}.%M") + val getterStatementString = """ + return %M( + ${getterCodeBlocks.joinToString(separator = ",\n")} + ) + """.trimIndent() + getterBuilder.addStatement(getterStatementString, *(getterArgsValue.toTypedArray())) + propertyBuilder.getter(getterBuilder.build()) + } + + if (property.setter != null) { + val setterBuilder = FunSpec.setterBuilder() + val mockUnitFunction = MemberName("com.careem.mockingbird.test", MOCK_UNIT) + val setterArgsValue = mutableListOf( + mockUnitFunction, + MemberName( + "", + adjustPropertyName(false, property.simpleName.getShortName()) + ), + MemberName("", PROPERTY_SETTER_VALUE), + PROPERTY_SETTER_VALUE + ) + + val v = mutableListOf().apply { + add("Property.%M to %L") + } + val args = v.joinToString(separator = ",") + val setterCodeBlocks = mutableListOf("methodName = ${PROPERTY}.%M") + setterCodeBlocks.add("arguments = mapOf($args)") + val setterStatementString = """ + return %M( + ${setterCodeBlocks.joinToString(separator = ",\n")} + ) + """.trimIndent() + setterBuilder + .addParameter("value", property.type.resolve().toTypeName()) + .addStatement(setterStatementString, *(setterArgsValue.toTypedArray())) + propertyBuilder + .mutable() + .setter(setterBuilder.build()) + } + + mockClassBuilder.addProperty(propertyBuilder.build()) + } + + private fun buildFunctionModifiers( + function: KSFunctionDeclaration + ): List { + return buildList { + getFunctionVisibility(function.modifiers)?.let { add(it) } + add(KModifier.OVERRIDE) + if (function.modifiers.contains(Modifier.SUSPEND)) { + add(KModifier.SUSPEND) + } + } + } + + private fun getFunctionVisibility(modifiers: Set) = + if (modifiers.contains(Modifier.INTERNAL)) { + KModifier.INTERNAL + } else if (modifiers.contains(Modifier.PRIVATE)) { + KModifier.PRIVATE + } else if (modifiers.contains(Modifier.PROTECTED)) { + KModifier.PROTECTED + } else if (modifiers.contains(Modifier.PUBLIC)) { + KModifier.PUBLIC + } else null + + @OptIn(KotlinPoetKspPreview::class) + private fun mockFunction( + mockClassBuilder: TypeSpec.Builder, + function: KSFunctionDeclaration, + isUnit: Boolean + ) { + logger.info("Mocking function") + val funBuilder = FunSpec.builder(function.simpleName.getShortName()) + .addModifiers(buildFunctionModifiers(function)) + for (valueParam in function.parameters) { + logger.info(valueParam.type.toString()) + funBuilder.addParameter(valueParam.name!!.getShortName(), valueParam.type.toTypeName()) + } + if (!isUnit) { + funBuilder.returns(function.returnType!!.toTypeName()) + } + funBuilder.addMockStatement(function, isUnit) + mockClassBuilder.addFunction( + funBuilder.build() + ) + } + + private fun FunSpec.Builder.addMockStatement(function: KSFunctionDeclaration, isUnit: Boolean) { + val mockFunction = if (isUnit) { + MOCK_UNIT + } else { + MOCK + } + val mockUnit = MemberName("com.careem.mockingbird.test", mockFunction) + val v = mutableListOf() + for (i in function.parameters.indices) { + v.add("Arg.%M to %L") + } + val args = v.joinToString(separator = ",") + val argsValue = mutableListOf(mockUnit, MemberName("", function.simpleName.getShortName())) + for (vp in function.parameters) { + argsValue.add(MemberName("", vp.name!!.getShortName())) + argsValue.add(vp.name!!.getShortName()) + } + logger.logging(argsValue.toString()) + val codeBlocks = mutableListOf("methodName = Method.%M") + if (args.isNotEmpty()) { + codeBlocks.add("arguments = mapOf($args)") + } + val statementString = """ + return %M( + ${codeBlocks.joinToString(separator = ",\n")} + ) + """.trimIndent() + + this.addStatement(statementString, *(argsValue.toTypedArray())) + } + + companion object { + private const val METHOD = "Method" + private const val ARG = "Arg" + private const val PROPERTY = "Property" + private const val MOCK_UNIT = "mockUnit" + private const val MOCK = "mock" + private const val PROPERTY_SETTER_VALUE = "value" + } + +} \ No newline at end of file diff --git a/mockingbird-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/mockingbird-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 0000000..c844749 --- /dev/null +++ b/mockingbird-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1,17 @@ +# +# Copyright Careem, an Uber Technologies Inc. company +# +# 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. +# + +com.careem.mockingbird.processor.GenerateMocksSymbolProcessorProvider \ No newline at end of file diff --git a/mockingbird/src/commonMain/kotlin/com/careem/mockingbird/test/Functions.kt b/mockingbird/src/commonMain/kotlin/com/careem/mockingbird/test/Functions.kt index 8ef4407..4326b29 100644 --- a/mockingbird/src/commonMain/kotlin/com/careem/mockingbird/test/Functions.kt +++ b/mockingbird/src/commonMain/kotlin/com/careem/mockingbird/test/Functions.kt @@ -52,11 +52,12 @@ public fun runWithTestMode(testMode: TestMode, testBlock: () -> Unit) { * @param methodName name of the method that you want to mock * @param arguments map between names and method arguments */ -public fun T.every( +public fun T.every( methodName: String, arguments: Map = emptyMap(), returns: () -> R ) { + if(this !is Mock) throw IllegalArgumentException("This object is not a mock") val uuid = this.uuid val value = returns() MockingBird.invocationRecorder().access { recorder -> @@ -73,11 +74,12 @@ public fun T.every( * @param methodName name of the method that you want to mock * @param arguments map between names and method arguments */ -public fun T.everyAnswers( +public fun T.everyAnswers( methodName: String, arguments: Map = emptyMap(), answer: (Invocation) -> R ) { + if(this !is Mock) throw IllegalArgumentException("This object is not a mock") val uuid = this.uuid MockingBird.invocationRecorder().access { recorder -> recorder.storeAnswer( @@ -95,12 +97,13 @@ public fun T.everyAnswers( * @param arguments map between names and method arguments * @param timeoutMillis milliseconds allowed to wait until the condition is considered false */ -public fun T.verify( +public fun T.verify( exactly: Int = 1, methodName: String, arguments: Map = emptyMap(), timeoutMillis: Long = 0L ) { + if(this !is Mock) throw IllegalArgumentException("This object is not a mock") val elapsedTime = atomic(0L) val run = atomic(true) while (run.value && elapsedTime.value < timeoutMillis) { diff --git a/mockingbird/src/commonMain/kotlin/com/careem/mockingbird/test/annotations/Mock.kt b/mockingbird/src/commonMain/kotlin/com/careem/mockingbird/test/annotations/Mock.kt new file mode 100644 index 0000000..a4892d6 --- /dev/null +++ b/mockingbird/src/commonMain/kotlin/com/careem/mockingbird/test/annotations/Mock.kt @@ -0,0 +1,5 @@ +package com.careem.mockingbird.test.annotations + +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.PROPERTY) +public annotation class Mock() diff --git a/samples/gradle/wrapper/gradle-wrapper.jar b/samples/gradle/wrapper/gradle-wrapper.jar index e708b1c..7454180 100644 Binary files a/samples/gradle/wrapper/gradle-wrapper.jar and b/samples/gradle/wrapper/gradle-wrapper.jar differ diff --git a/samples/gradle/wrapper/gradle-wrapper.properties b/samples/gradle/wrapper/gradle-wrapper.properties index a0f7639..ae04661 100644 --- a/samples/gradle/wrapper/gradle-wrapper.properties +++ b/samples/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/samples/gradlew b/samples/gradlew index 4f906e0..1b6c787 100755 --- a/samples/gradlew +++ b/samples/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/samples/kspsample/build.gradle.kts b/samples/kspsample/build.gradle.kts new file mode 100644 index 0000000..64ef089 --- /dev/null +++ b/samples/kspsample/build.gradle.kts @@ -0,0 +1,51 @@ +/** + * + * Copyright Careem, an Uber Technologies Inc. company + * + * 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 groovy.lang.Closure + +plugins{ + id("org.jetbrains.kotlin.multiplatform") + id("com.google.devtools.ksp") version libs.versions.kspVersion.get() +} + +apply(from = "../../utils.gradle") +val setupMultiplatformLibrary: Closure by ext +setupMultiplatformLibrary(project, false, false) + +kotlin { + sourceSets { + val commonMain by getting { + dependencies { + implementation(project(":common-sample")) + implementation(project(":common:sample")) + implementation(libs.touchlab.stately.isolate) + } + } + val commonTest by getting { + dependencies { + implementation("com.careem.mockingbird:mockingbird") + } + } + } +} + +dependencies { + "kspJvmTest"("com.careem.mockingbird:mockingbird-processor") + "kspIosSimulatorArm64Test"("com.careem.mockingbird:mockingbird-processor") + "kspIosX64Test"("com.careem.mockingbird:mockingbird-processor") + "kspIosArm64Test"("com.careem.mockingbird:mockingbird-processor") +} + diff --git a/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/InnerInnerInterface.kt b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/InnerInnerInterface.kt new file mode 100644 index 0000000..602e2ee --- /dev/null +++ b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/InnerInnerInterface.kt @@ -0,0 +1,22 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.kspsample + +interface InnerInnerInterface { + val yo3: Int + fun deepFoo(deepArg: String) +} \ No newline at end of file diff --git a/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/InnerInterface.kt b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/InnerInterface.kt new file mode 100644 index 0000000..61f42b4 --- /dev/null +++ b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/InnerInterface.kt @@ -0,0 +1,23 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.kspsample + +interface InnerInterface: InnerInnerInterface { + var yo2: String + fun foo(fooArg: String) + fun foo2(fooArg2: String) +} \ No newline at end of file diff --git a/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/InterfaceWithGenerics.kt b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/InterfaceWithGenerics.kt new file mode 100644 index 0000000..ab7b9d6 --- /dev/null +++ b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/InterfaceWithGenerics.kt @@ -0,0 +1,26 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.kspsample + +interface InterfaceWithGenerics { + fun foo(fooArg: List>): List> + fun complexFoo(fooArg: List>, List>>): List> + fun foo(fooArg: Map): Map + + var g1: List + var g2: Map +} \ No newline at end of file diff --git a/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/InternalSampleInterface.kt b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/InternalSampleInterface.kt new file mode 100644 index 0000000..c10b473 --- /dev/null +++ b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/InternalSampleInterface.kt @@ -0,0 +1,21 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.kspsample + +internal interface InternalSampleInterface { + fun thisIsInternal(param: String) +} \ No newline at end of file diff --git a/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/JavaTypes.kt b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/JavaTypes.kt new file mode 100644 index 0000000..8450345 --- /dev/null +++ b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/JavaTypes.kt @@ -0,0 +1,44 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.kspsample + +interface JavaTypes { + fun byte(b: Byte) + fun shorth(s: Short) + fun int(i: Int) + fun long(l: Long) + fun char(c: Char) + fun float(f: Float) + fun double(d: Double) + fun boolean(b: Boolean) + + fun string(s: String) + fun number(s: Number) + fun obj(o: Any) + fun comparable(o: Comparable) + fun charsequance(cs: CharSequence) + fun throwable(t: Throwable) + + fun iterator(i: Iterator) + fun iterable(i: Iterable) + fun collection(c: Collection) + fun set(s: Set) + fun list(l: List) + fun listIterator(li: ListIterator) + fun map(m: Map) + fun mapEntry(me: Map.Entry) +} \ No newline at end of file diff --git a/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/LambdaSample.kt b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/LambdaSample.kt new file mode 100644 index 0000000..56d9d6e --- /dev/null +++ b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/LambdaSample.kt @@ -0,0 +1,28 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.kspsample + +interface LambdaSample { + fun lambda00(param: String, lambda: () -> Int) + fun lambda01(param: String, lambda: () -> Unit) + fun lambda10(param: String, lambda: (Int) -> Unit) + fun lambda11(param: String, lambda: (Int) -> Double) + fun lambda2(param: String, lambda: (String, Int) -> Unit) + fun lambda3(param: String, lambda: (String, Int, Map) -> Unit) + fun lambda4(param: String, lambda: (Boolean, Int, String, Double) -> Unit) + fun lambda5(param: String, lambda: (Boolean, Int, String, Double, List) -> Unit) +} \ No newline at end of file diff --git a/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/Mock1.kt b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/Mock1.kt new file mode 100644 index 0000000..bf460fa --- /dev/null +++ b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/Mock1.kt @@ -0,0 +1,26 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.kspsample + +interface Mock1 { + fun foo(): Boolean + fun foo1() + fun foo2(string: String) + fun foo3(string2: String, someOtherParam: Boolean) + fun nullableFoo(nullString: String?): String? + var nullableProperty: String? +} \ No newline at end of file diff --git a/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/MockWithExternalDependencies.kt b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/MockWithExternalDependencies.kt new file mode 100644 index 0000000..4d300be --- /dev/null +++ b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/MockWithExternalDependencies.kt @@ -0,0 +1,23 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.kspsample + +import com.careem.mockingbird.common.sample.SampleData + +interface MockWithExternalDependencies { + fun foo(sampleData: SampleData) +} \ No newline at end of file diff --git a/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/MultipleGetterProperties.kt b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/MultipleGetterProperties.kt new file mode 100644 index 0000000..5493117 --- /dev/null +++ b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/MultipleGetterProperties.kt @@ -0,0 +1,23 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.kspsample + + +public interface MultipleGetterProperties { + public val isA: Boolean + public val isB: Boolean +} \ No newline at end of file diff --git a/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/OuterInterface.kt b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/OuterInterface.kt new file mode 100644 index 0000000..0a67d7e --- /dev/null +++ b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/OuterInterface.kt @@ -0,0 +1,26 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.kspsample + +interface OuterInterface : InnerInterface { + var yo: String + var yoo: String + override fun foo(fooArg: String) + fun foo1(fooArg1: String) + fun foo2(fooArg12: String, fooArg22: String) + fun thr(throwable: Throwable) +} \ No newline at end of file diff --git a/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/PippoSample.kt b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/PippoSample.kt new file mode 100644 index 0000000..d5e19da --- /dev/null +++ b/samples/kspsample/src/commonMain/kotlin/com/careem/mockingbird/kspsample/PippoSample.kt @@ -0,0 +1,30 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.kspsample + +interface PippoSample { + val currentSession: Int + var currentMutableSession: Int + fun showRandom(): Boolean + fun sayHi() + fun sayHiWith(param: String) + fun sayHiWith(param: String, someOtherParam: Boolean) + fun sayHiWithCommonParam(param: String, intParam: Int) + fun sayHiWith(param: String, map: Map) + fun sayHiWith(param: String, entry: Map.Entry) + suspend fun thisIsSuspend(param: String, intParam: Int) +} \ No newline at end of file diff --git a/samples/kspsample/src/commonTest/kotlin/com/careem/mockingbird/kspsample/KspSampleTest.kt b/samples/kspsample/src/commonTest/kotlin/com/careem/mockingbird/kspsample/KspSampleTest.kt new file mode 100644 index 0000000..6a75a3d --- /dev/null +++ b/samples/kspsample/src/commonTest/kotlin/com/careem/mockingbird/kspsample/KspSampleTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright Careem, an Uber Technologies Inc. company + * + * 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. + */ + +package com.careem.mockingbird.kspsample + +import com.careem.mockingbird.test.annotations.Mock +import kotlin.test.Test +import kotlin.test.assertNotNull + +class KspSampleTest { + @Mock + val pippoMock: PippoSample = PippoSampleMock() + + @Mock + val outerInterface: OuterInterface = OuterInterfaceMock() + + @Mock + val multipleGetterProperties: MultipleGetterProperties = MultipleGetterPropertiesMock() + + @Mock + val mockWithExternalDependencies: MockWithExternalDependencies = MockWithExternalDependenciesMock() + + @Mock + val mock1: Mock1 = Mock1Mock() + + @Mock + val lambdaSample: LambdaSample = LambdaSampleMock() + + @Mock + val javaTypes: JavaTypes = JavaTypesMock() + + @Mock + private val internalSampleInterface: InternalSampleInterface = InternalSampleInterfaceMock() + + @Mock + val interfaceWithGenerics: InterfaceWithGenerics = InterfaceWithGenericsMock() + + @Mock + val innerInterface: InnerInterface = InnerInterfaceMock() + + @Mock + val innerInnerInterface: InnerInnerInterface = InnerInnerInterfaceMock() + + @Test + fun testGeneratedTargetProjectMock() { + assertNotNull(pippoMock) + } + +} + diff --git a/samples/settings.gradle.kts b/samples/settings.gradle.kts index 3bcd711..9422714 100644 --- a/samples/settings.gradle.kts +++ b/samples/settings.gradle.kts @@ -1,6 +1,3 @@ -import org.gradle.initialization.DependenciesAccessors -import org.gradle.kotlin.dsl.support.serviceOf - /** * * Copyright Careem, an Uber Technologies Inc. company @@ -29,6 +26,7 @@ dependencyResolutionManagement { } include(":sample") +include(":kspsample") include(":common-sample") include(":common:sample") @@ -36,5 +34,6 @@ includeBuild("..") { dependencySubstitution { substitute(module("com.careem.mockingbird:mockingbird")).using(project(":mockingbird")) substitute(module("com.careem.mockingbird:mockingbird-compiler")).using(project(":mockingbird-compiler")) + substitute(module("com.careem.mockingbird:mockingbird-processor")).using(project(":mockingbird-processor")) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 878d77e..b826c0a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,6 +19,7 @@ enableFeaturePreview("VERSION_CATALOGS") include(":mockingbird") include(":mockingbird-compiler") +include(":mockingbird-processor") dependencyResolutionManagement { versionCatalogs { diff --git a/versions.toml b/versions.toml index 40bc9ec..1dadfba 100644 --- a/versions.toml +++ b/versions.toml @@ -1,11 +1,13 @@ [versions] kotlin = "1.6.21" -kotlinTarget = "1.5" +kspVersion = "1.6.21-1.0.6" +kotlinTarget = "1.6" junit = "4.13.1" jacoco = "0.8.8" stately = "1.2.1" atomicFu = "0.17.2" kotlinPoet = "1.11.0" +kotlinPoetKsp = "1.10.2" kotlinxMetadata = "0.4.2" mockk = "1.12.3" @@ -20,8 +22,11 @@ kotlin-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = junit-junit = { module = "junit:junit", version.ref = "junit" } kotlin-reflectjvm = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } square-kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinPoet" } +square-kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinPoetKsp" } square-kotlinpoet-metadata = { module = "com.squareup:kotlinpoet-metadata", version.ref = "kotlinPoet" } +square-kotlinpoet-metadata-specs = { module = "com.squareup:kotlinpoet-metadata-specs", version.ref = "kotlinPoet" } kotlinx-metadata-jvm = { module = "org.jetbrains.kotlinx:kotlinx-metadata-jvm", version.ref = "kotlinxMetadata" } +google-ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "kspVersion" } kotlinx-atomicfu-gradle = { module = "org.jetbrains.kotlinx:atomicfu-gradle-plugin", version.ref = "atomicFu" } kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }