init research
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import org.junit.Before
|
||||
import java.util.Properties
|
||||
|
||||
abstract class AbstractDataFramePluginIntegrationTest {
|
||||
protected val kotlinVersion = TestData.kotlinVersion
|
||||
protected lateinit var dataframeJarPath: String
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
val properties = Properties().also {
|
||||
it.load(javaClass.getResourceAsStream("df.properties"))
|
||||
}
|
||||
dataframeJarPath = properties.getProperty("DATAFRAME_JAR")
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.gradle.testkit.runner.TaskOutcome
|
||||
import org.jetbrains.kotlinx.dataframe.DataFrame
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
class ApiChangesDetectionTest : AbstractDataFramePluginIntegrationTest() {
|
||||
|
||||
// GenerateDataSchemaTask::class,
|
||||
// DefaultReadCsvMethod::class,
|
||||
// DefaultReadJsonMethod::class
|
||||
@Test
|
||||
fun `cast api`() {
|
||||
compiles {
|
||||
"""
|
||||
import ${DataFrame::class.qualifiedName!!}
|
||||
import org.jetbrains.kotlinx.dataframe.api.cast
|
||||
|
||||
interface Marker
|
||||
|
||||
fun DataFrame<*>.resolveApi() {
|
||||
cast<Marker>()
|
||||
cast<Marker>(true)
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateDataSchemaTask::class,
|
||||
// DefaultReadJsonMethod::class
|
||||
@Test
|
||||
fun `read json api`() {
|
||||
compiles {
|
||||
"""
|
||||
import ${DataFrame::class.qualifiedName!!}
|
||||
import org.jetbrains.kotlinx.dataframe.io.readJson
|
||||
|
||||
fun DataFrame<*>.resolveApi(s: String) {
|
||||
DataFrame.readJson(s)
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateDataSchemaTask::class,
|
||||
// DefaultReadCsvMethod::class,
|
||||
@Test
|
||||
fun `read csv api`() {
|
||||
compiles {
|
||||
"""
|
||||
import ${DataFrame::class.qualifiedName!!}
|
||||
import org.jetbrains.kotlinx.dataframe.io.readCSV
|
||||
|
||||
fun DataFrame<*>.resolveApi(s: String, ch: Char) {
|
||||
DataFrame.readCSV(s, ch)
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
private fun compiles(code: () -> String) {
|
||||
val (_, result) = runGradleBuild(":build") { buildDir ->
|
||||
val kotlin = File(buildDir, "src/main/kotlin").also { it.mkdirs() }
|
||||
val main = File(kotlin, "Main.kt")
|
||||
main.writeText(code())
|
||||
"""
|
||||
plugins {
|
||||
kotlin("jvm") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("$dataframeJarPath"))
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":build")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
}
|
||||
+685
@@ -0,0 +1,685 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import io.kotest.assertions.asClue
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.gradle.testkit.runner.TaskOutcome
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.sql.Connection
|
||||
import java.sql.DriverManager
|
||||
|
||||
class SchemaGeneratorPluginIntegrationTest : AbstractDataFramePluginIntegrationTest() {
|
||||
private companion object {
|
||||
private const val FIRST_NAME = "first.csv"
|
||||
private const val SECOND_NAME = "second.csv"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `compileKotlin depends on generateAll task`() {
|
||||
val (_, result) = runGradleBuild(":compileKotlin") { buildDir ->
|
||||
File(buildDir, FIRST_NAME).also { it.writeText(TestData.csvSample) }
|
||||
File(buildDir, SECOND_NAME).also { it.writeText(TestData.csvSample) }
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("$dataframeJarPath"))
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = file("$FIRST_NAME")
|
||||
name = "Test"
|
||||
packageName = "org.test"
|
||||
}
|
||||
schema {
|
||||
data = file("$SECOND_NAME")
|
||||
name = "Schema"
|
||||
packageName = "org.test"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":generateDataFrameTest")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
result.task(":generateDataFrameSchema")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `packageName convention is 'dataframe'`() {
|
||||
val (dir, result) = runGradleBuild(":build") { buildDir ->
|
||||
val dataFile = File(buildDir, TestData.csvName)
|
||||
dataFile.writeText(TestData.csvSample)
|
||||
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("$dataframeJarPath"))
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = file("${TestData.csvName}")
|
||||
name = "Data"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":generateDataFrameData")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
File(dir, "build/generated/dataframe").walkBottomUp().toList().asClue {
|
||||
File(dir, "build/generated/dataframe/main/kotlin/dataframe/Data.Generated.kt").exists() shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fallback all properties to conventions`() {
|
||||
val (_, result) = runGradleBuild(":build") { buildDir ->
|
||||
val dataFile = File(buildDir, TestData.csvName)
|
||||
dataFile.writeText(TestData.csvSample)
|
||||
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("$dataframeJarPath"))
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = file("${TestData.csvName}")
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":generateDataFrameData")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generated schemas resolved`() {
|
||||
val (_, result) = runGradleBuild(":build") { buildDir ->
|
||||
val dataFile = File(buildDir, "data.csv")
|
||||
dataFile.writeText(TestData.csvSample)
|
||||
|
||||
val kotlin = File(buildDir, "src/main/kotlin").also { it.mkdirs() }
|
||||
val main = File(kotlin, "Main.kt")
|
||||
main.writeText(
|
||||
"""
|
||||
import org.example.Schema
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven(url="https://jitpack.io")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("$dataframeJarPath"))
|
||||
}
|
||||
|
||||
kotlin.sourceSets.getByName("main").kotlin.srcDir("build/generated/ksp/main/kotlin/")
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = file("${TestData.csvName}")
|
||||
name = "org.example.Schema"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":build")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generated schemas resolved in jvmMain source set for multiplatform project`() {
|
||||
val (_, result) = runGradleBuild(":build") { buildDir ->
|
||||
val dataFile = File(buildDir, "data.csv")
|
||||
dataFile.writeText(TestData.csvSample)
|
||||
|
||||
val kotlin = File(buildDir, "src/jvmMain/kotlin").also { it.mkdirs() }
|
||||
val main = File(kotlin, "Main.kt")
|
||||
main.writeText(
|
||||
"""
|
||||
import org.example.Schema
|
||||
""".trimIndent(),
|
||||
)
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
implementation(files("$dataframeJarPath"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = file("${TestData.csvName}")
|
||||
name = "org.example.Schema"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":build")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun `kotlin identifiers generated from csv names`() {
|
||||
fun escapeDoubleQuotes(it: Char) = if (it == '"') "\"\"" else it.toString()
|
||||
|
||||
val (_, result) = runGradleBuild(":build") { buildDir ->
|
||||
val filename = "data.csv"
|
||||
val dataFile = File(buildDir, filename)
|
||||
val notSupportedChars = setOf('\n', '\r')
|
||||
(Char.MIN_VALUE..Char.MAX_VALUE).asSequence()
|
||||
.filterNot { it in notSupportedChars }
|
||||
.chunked(100) {
|
||||
it.joinToString(
|
||||
separator = "",
|
||||
prefix = "\"",
|
||||
postfix = "\"",
|
||||
transform = ::escapeDoubleQuotes,
|
||||
)
|
||||
}.let {
|
||||
dataFile.writeText(it.joinToString(",") + "\n" + (0 until it.count()).joinToString(","))
|
||||
}
|
||||
|
||||
val kotlin = File(buildDir, "src/main/kotlin").also { it.mkdirs() }
|
||||
val main = File(kotlin, "Main.kt")
|
||||
main.writeText(
|
||||
"""
|
||||
import org.jetbrains.kotlinx.dataframe.DataFrame
|
||||
import org.jetbrains.kotlinx.dataframe.annotations.DataSchema
|
||||
import org.jetbrains.kotlinx.dataframe.io.read
|
||||
import org.jetbrains.kotlinx.dataframe.api.cast
|
||||
import org.jetbrains.kotlinx.dataframe.api.filter
|
||||
|
||||
fun main() {
|
||||
val df = DataFrame.read("${TestData.csvName}").cast<Schema>()
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("$dataframeJarPath"))
|
||||
}
|
||||
|
||||
kotlin.sourceSets.getByName("main").kotlin.srcDir("build/generated/ksp/main/kotlin/")
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = "${TestData.csvName}"
|
||||
name = "Schema"
|
||||
packageName = ""
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":build")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preprocessor generates extensions for DataSchema`() {
|
||||
val (_, result) = runGradleBuild(":build") { buildDir ->
|
||||
val dataFile = File(buildDir, "data.csv")
|
||||
dataFile.writeText(TestData.csvSample)
|
||||
|
||||
val kotlin = File(buildDir, "src/main/kotlin").also { it.mkdirs() }
|
||||
val main = File(kotlin, "Main.kt")
|
||||
main.writeText(
|
||||
"""
|
||||
import org.jetbrains.kotlinx.dataframe.DataFrame
|
||||
import org.jetbrains.kotlinx.dataframe.annotations.DataSchema
|
||||
import org.jetbrains.kotlinx.dataframe.io.read
|
||||
import org.jetbrains.kotlinx.dataframe.api.cast
|
||||
import org.jetbrains.kotlinx.dataframe.api.filter
|
||||
|
||||
@DataSchema
|
||||
interface MySchema {
|
||||
val age: Int
|
||||
}
|
||||
|
||||
fun main() {
|
||||
val df = DataFrame.read("${TestData.csvName}").cast<MySchema>()
|
||||
val df1 = df.filter { age != null }
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("$dataframeJarPath"))
|
||||
}
|
||||
|
||||
kotlin.sourceSets.getByName("main").kotlin.srcDir("build/generated/ksp/main/kotlin/")
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":build")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preprocessor imports schema from local file`() {
|
||||
val (_, result) = runGradleBuild(":build") { buildDir ->
|
||||
val dataFile = File(buildDir, "data.csv")
|
||||
dataFile.writeText(TestData.csvSample)
|
||||
|
||||
val kotlin = File(buildDir, "src/main/kotlin").also { it.mkdirs() }
|
||||
val main = File(kotlin, "Main.kt")
|
||||
main.writeText(
|
||||
"""
|
||||
@file:ImportDataSchema(name = "MySchema", path = "${TestData.csvName}")
|
||||
|
||||
package test
|
||||
|
||||
import org.jetbrains.kotlinx.dataframe.annotations.ImportDataSchema
|
||||
import org.jetbrains.kotlinx.dataframe.api.filter
|
||||
|
||||
fun main() {
|
||||
val df = MySchema.readCsv()
|
||||
val df1 = df.filter { age != null }
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("$dataframeJarPath"))
|
||||
}
|
||||
|
||||
kotlin.sourceSets.getByName("main").kotlin.srcDir("build/generated/ksp/main/kotlin/")
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":build")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
/* TODO: test is broken
|
||||
e: file://test3901867314473689900/src/main/kotlin/Main.kt:12:43 Unresolved reference: readSqlTable
|
||||
e: file://test3901867314473689900/src/main/kotlin/Main.kt:13:43 Unresolved reference: DbConnectionConfig
|
||||
e: file://test3901867314473689900/src/main/kotlin/Main.kt:19:28 Unresolved reference: readSqlTable
|
||||
e: file://test3901867314473689900/src/main/kotlin/Main.kt:20:21 Unresolved reference: age
|
||||
e: file://test3901867314473689900/src/main/kotlin/Main.kt:22:29 Unresolved reference: readSqlTable
|
||||
e: file://test3901867314473689900/src/main/kotlin/Main.kt:23:22 Unresolved reference: age
|
||||
e: file://test3901867314473689900/src/main/kotlin/Main.kt:25:24 Unresolved reference: DbConnectionConfig
|
||||
e: file://test3901867314473689900/src/main/kotlin/Main.kt:26:29 Unresolved reference: readSqlTable
|
||||
e: file://test3901867314473689900/src/main/kotlin/Main.kt:27:22 Unresolved reference: age
|
||||
e: file://test3901867314473689900/src/main/kotlin/Main.kt:29:29 Unresolved reference: readSqlTable
|
||||
e: file://test3901867314473689900/src/main/kotlin/Main.kt:30:22 Unresolved reference: age
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
fun `preprocessor imports schema from database`() {
|
||||
val connectionUrl = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=MySQL;DATABASE_TO_UPPER=false"
|
||||
DriverManager.getConnection(connectionUrl).use {
|
||||
val (_, result) = runGradleBuild(":build") { buildDir ->
|
||||
createTestDatabase(it)
|
||||
|
||||
val kotlin = File(buildDir, "src/main/kotlin").also { it.mkdirs() }
|
||||
val main = File(kotlin, "Main.kt")
|
||||
// this is a copy of the code snippet in the
|
||||
// DataFrameJdbcSymbolProcessorTest.`schema extracted via readFromDB method is resolved`
|
||||
main.writeText(
|
||||
"""
|
||||
@file:ImportDataSchema(name = "Customer", path = "$connectionUrl")
|
||||
|
||||
package test
|
||||
|
||||
import org.jetbrains.kotlinx.dataframe.annotations.ImportDataSchema
|
||||
import org.jetbrains.kotlinx.dataframe.api.filter
|
||||
import org.jetbrains.kotlinx.dataframe.DataFrame
|
||||
import org.jetbrains.kotlinx.dataframe.api.cast
|
||||
import java.sql.Connection
|
||||
import java.sql.DriverManager
|
||||
import java.sql.SQLException
|
||||
import org.jetbrains.kotlinx.dataframe.io.readSqlTable
|
||||
import org.jetbrains.kotlinx.dataframe.io.DbConnectionConfig
|
||||
|
||||
fun main() {
|
||||
Class.forName("org.h2.Driver")
|
||||
val tableName = "Customer"
|
||||
DriverManager.getConnection("$connectionUrl").use { connection ->
|
||||
val df = DataFrame.readSqlTable(connection, tableName).cast<Customer>()
|
||||
df.filter { age != null && age > 30 }
|
||||
|
||||
val df1 = DataFrame.readSqlTable(connection, tableName, 1).cast<Customer>()
|
||||
df1.filter { age != null && age > 30 }
|
||||
|
||||
val dbConfig = DbConnectionConfig(url = "$connectionUrl")
|
||||
val df2 = DataFrame.readSqlTable(dbConfig, tableName).cast<Customer>()
|
||||
df2.filter { age != null && age > 30 }
|
||||
|
||||
val df3 = DataFrame.readSqlTable(dbConfig, tableName, 1).cast<Customer>()
|
||||
df3.filter { age != null && age > 30 }
|
||||
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("$dataframeJarPath"))
|
||||
}
|
||||
|
||||
kotlin.sourceSets.getByName("main").kotlin.srcDir("build/generated/ksp/main/kotlin/")
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":build")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTestDatabase(connection: Connection) {
|
||||
// Crate table Customer
|
||||
connection.createStatement().execute(
|
||||
"""
|
||||
CREATE TABLE Customer (
|
||||
id INT PRIMARY KEY,
|
||||
name VARCHAR(50),
|
||||
age INT
|
||||
)
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
// Create table Sale
|
||||
connection.createStatement().execute(
|
||||
"""
|
||||
CREATE TABLE Sale (
|
||||
id INT PRIMARY KEY,
|
||||
customerId INT,
|
||||
amount DECIMAL(10, 2)
|
||||
)
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
// add data to the Customer table
|
||||
connection.createStatement().execute("INSERT INTO Customer (id, name, age) VALUES (1, 'John', 40)")
|
||||
connection.createStatement().execute("INSERT INTO Customer (id, name, age) VALUES (2, 'Alice', 25)")
|
||||
connection.createStatement().execute("INSERT INTO Customer (id, name, age) VALUES (3, 'Bob', 47)")
|
||||
|
||||
// add data to the Sale table
|
||||
connection.createStatement().execute("INSERT INTO Sale (id, customerId, amount) VALUES (1, 1, 100.50)")
|
||||
connection.createStatement().execute("INSERT INTO Sale (id, customerId, amount) VALUES (2, 2, 50.00)")
|
||||
connection.createStatement().execute("INSERT INTO Sale (id, customerId, amount) VALUES (3, 1, 75.25)")
|
||||
connection.createStatement().execute("INSERT INTO Sale (id, customerId, amount) VALUES (4, 3, 35.15)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generated code compiles in explicit api mode`() {
|
||||
val (_, result) = runGradleBuild(":build") { buildDir ->
|
||||
val dataFile = File(buildDir, TestData.csvName)
|
||||
dataFile.writeText(TestData.csvSample)
|
||||
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("$dataframeJarPath"))
|
||||
}
|
||||
|
||||
kotlin {
|
||||
explicitApi()
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = file("${TestData.csvName}")
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":generateDataFrameData")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun `plugin doesn't break multiplatform build without JVM`() {
|
||||
val (_, result) = runGradleBuild(":build") { buildDir ->
|
||||
val dataFile = File(buildDir, TestData.csvName)
|
||||
val kotlin = File(buildDir, "src/jsMain/kotlin").also { it.mkdirs() }
|
||||
val main = File(kotlin, "Main.kt")
|
||||
main.writeText(
|
||||
"""
|
||||
fun main() {
|
||||
console.log("Hello, Kotlin/JS!")
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
dataFile.writeText(TestData.csvSample)
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
js(IR) {
|
||||
browser()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = file("${TestData.csvName}")
|
||||
name = "Schema"
|
||||
packageName = ""
|
||||
src = buildDir
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":build")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `plugin doesn't break multiplatform build with JVM`() {
|
||||
val (_, result) = runGradleBuild(":build") { buildDir ->
|
||||
val dataFile = File(buildDir, TestData.csvName)
|
||||
val kotlin = File(buildDir, "src/jvmMain/kotlin").also { it.mkdirs() }
|
||||
val main = File(kotlin, "Main.kt")
|
||||
main.writeText(
|
||||
"""
|
||||
fun main() {
|
||||
println("Hello, Kotlin/JVM!")
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
dataFile.writeText(TestData.csvSample)
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
jvm()
|
||||
}
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = file("${TestData.csvName}")
|
||||
name = "Schema"
|
||||
packageName = ""
|
||||
src = buildDir
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":build")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `companion object for csv compiles`() {
|
||||
testCompanionObject(TestData.csvName, TestData.csvSample)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `companion object for json compiles`() {
|
||||
testCompanionObject(TestData.jsonName, TestData.jsonSample)
|
||||
}
|
||||
|
||||
private fun testCompanionObject(dataName: String, dataSample: String) {
|
||||
val (_, result) = runGradleBuild(":build") { buildDir ->
|
||||
val dataFile = File(buildDir, dataName)
|
||||
dataFile.writeText(dataSample)
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("$dataframeJarPath"))
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = file("$dataName")
|
||||
name = "Schema"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":build")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
DATAFRAME_JAR=%DATAFRAME_JAR%
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import com.google.devtools.ksp.gradle.KspExtension
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.getByType
|
||||
import java.util.Properties
|
||||
|
||||
@Suppress("unused")
|
||||
public class ConvenienceSchemaGeneratorPlugin : Plugin<Project> {
|
||||
public companion object {
|
||||
/**
|
||||
* (boolean, default `true`) whether to add KSP plugin
|
||||
*/
|
||||
public const val PROP_ADD_KSP: String = "kotlin.dataframe.add.ksp"
|
||||
|
||||
/**
|
||||
* (string, default `null`) comma-delimited list of configurations to add KSP processing to.
|
||||
* Defaults to guessing configurations based on which kotlin plugin is applied (jvm or multiplatform)
|
||||
*/
|
||||
public const val PROP_KSP_CONFIGS: String = "kotlin.dataframe.ksp.configs"
|
||||
}
|
||||
|
||||
override fun apply(target: Project) {
|
||||
val property = target.findProperty(PROP_ADD_KSP)?.toString()
|
||||
var addKsp = true
|
||||
|
||||
if (property != null) {
|
||||
if (property.equals("true", ignoreCase = true) || property.equals("false", ignoreCase = true)) {
|
||||
addKsp = property.toBoolean()
|
||||
} else {
|
||||
target.logger.warn(
|
||||
"Invalid value '$property' for '$PROP_ADD_KSP' property. Defaulting to '$addKsp'. Please use 'true' or 'false'.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val properties = Properties()
|
||||
properties.load(javaClass.getResourceAsStream("plugin.properties"))
|
||||
val preprocessorVersion = properties.getProperty("PREPROCESSOR_VERSION")
|
||||
|
||||
// regardless whether we add KSP or the user adds it, when it's added,
|
||||
// configure it to depend on symbol-processor-all
|
||||
target.plugins.whenPluginAdded {
|
||||
if (this::class.qualifiedName?.contains("com.google.devtools.ksp") == true) {
|
||||
val isMultiplatform by lazy {
|
||||
when {
|
||||
target.plugins.hasPlugin("org.jetbrains.kotlin.jvm") -> false
|
||||
|
||||
target.plugins.hasPlugin("org.jetbrains.kotlin.multiplatform") -> true
|
||||
|
||||
else -> {
|
||||
target.logger.warn(
|
||||
"Kotlin plugin must be applied first so we know whether to use multiplatform configurations or not",
|
||||
)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
val overriddenConfigs = target.findProperty(PROP_KSP_CONFIGS)
|
||||
?.let { (it as String) }
|
||||
?.split(",")
|
||||
?.map { it.trim() }
|
||||
val configs = when {
|
||||
overriddenConfigs != null -> overriddenConfigs
|
||||
isMultiplatform -> listOf("kspJvm", "kspJvmTest")
|
||||
else -> listOf("ksp", "kspTest")
|
||||
}
|
||||
|
||||
val cfgsToAdd = configs.toMutableSet()
|
||||
|
||||
configs.forEach { cfg ->
|
||||
target.configurations.findByName(cfg)?.apply {
|
||||
cfgsToAdd.remove(cfg)
|
||||
dependencies.add(
|
||||
target.dependencies.create(
|
||||
"org.jetbrains.kotlinx.dataframe:symbol-processor-all:$preprocessorVersion",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
target.gradle.projectsEvaluated {
|
||||
cfgsToAdd.forEach { cfg ->
|
||||
target.logger.warn(
|
||||
"Configuration '$cfg' was never found. Please make sure the KSP plugin is applied.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
target.logger.info("Added DataFrame dependency to the KSP plugin.")
|
||||
target.extensions.getByType<KspExtension>().arg(
|
||||
"dataframe.resolutionDir",
|
||||
target.projectDir.absolutePath,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (addKsp) {
|
||||
target.plugins.apply(KspPluginApplier::class.java)
|
||||
} else {
|
||||
target.logger.warn(
|
||||
"Plugin 'org.jetbrains.kotlinx.dataframe' comes bundled with its own version of KSP which is " +
|
||||
"currently disabled as 'kotlin.dataframe.add.ksp' is set to 'false' in a 'properties' file. " +
|
||||
"Either set 'kotlin.dataframe.add.ksp' to 'true' or add the plugin 'com.google.devtools.ksp' " +
|
||||
"manually.",
|
||||
)
|
||||
}
|
||||
target.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
public class DeprecatingSchemaGeneratorPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
target.logger.warn(
|
||||
"DEPRECATION: Replace plugin id(\"org.jetbrains.kotlin.plugin.dataframe\") and kotlin(\"plugin.dataframe\") with id(\"org.jetbrains.kotlinx.dataframe\").",
|
||||
)
|
||||
target.plugins.apply(ConvenienceSchemaGeneratorPlugin::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the KSP plugin in the target project.
|
||||
*/
|
||||
internal class KspPluginApplier : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
val properties = Properties()
|
||||
properties.load(javaClass.getResourceAsStream("plugin.properties"))
|
||||
target.plugins.apply("com.google.devtools.ksp")
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
public enum class DataSchemaVisibility {
|
||||
INTERNAL,
|
||||
IMPLICIT_PUBLIC,
|
||||
EXPLICIT_PUBLIC,
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import java.io.File
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
|
||||
internal fun extractFileName(url: URL): String? =
|
||||
url.path.takeIf { it.isNotEmpty() }
|
||||
?.substringAfterLast("/")
|
||||
?.substringBeforeLast(".")
|
||||
|
||||
internal fun extractFileName(file: File): String = file.nameWithoutExtension
|
||||
|
||||
internal fun extractFileName(path: String): String? =
|
||||
try {
|
||||
val url = URL(path)
|
||||
extractFileName(url)
|
||||
} catch (e: MalformedURLException) {
|
||||
val file = File(path)
|
||||
extractFileName(file)
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import java.io.File
|
||||
|
||||
internal fun File.isMiddlePackage(): Boolean = isDirectory && (listFiles()?.singleOrNull()?.isDirectory ?: false)
|
||||
|
||||
internal fun File.findDeepestCommonSubdirectory(): File {
|
||||
if (!exists()) return this
|
||||
return walkTopDown().filterNot { it.isMiddlePackage() }.first()
|
||||
}
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.provider.SetProperty
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.jetbrains.kotlinx.dataframe.codeGen.CodeGenerator
|
||||
import org.jetbrains.kotlinx.dataframe.codeGen.MarkerVisibility
|
||||
import org.jetbrains.kotlinx.dataframe.codeGen.NameNormalizer
|
||||
import org.jetbrains.kotlinx.dataframe.impl.codeGen.CodeGenerationReadResult
|
||||
import org.jetbrains.kotlinx.dataframe.impl.codeGen.DfReadResult
|
||||
import org.jetbrains.kotlinx.dataframe.impl.codeGen.from
|
||||
import org.jetbrains.kotlinx.dataframe.impl.codeGen.toStandaloneSnippet
|
||||
import org.jetbrains.kotlinx.dataframe.impl.codeGen.urlCodeGenReader
|
||||
import org.jetbrains.kotlinx.dataframe.impl.codeGen.urlDfReader
|
||||
import org.jetbrains.kotlinx.dataframe.io.ArrowFeather
|
||||
import org.jetbrains.kotlinx.dataframe.io.CsvDeephaven
|
||||
import org.jetbrains.kotlinx.dataframe.io.Excel
|
||||
import org.jetbrains.kotlinx.dataframe.io.JSON
|
||||
import org.jetbrains.kotlinx.dataframe.io.OpenApi
|
||||
import org.jetbrains.kotlinx.dataframe.io.TsvDeephaven
|
||||
import org.jetbrains.kotlinx.dataframe.io.isUrl
|
||||
import org.jetbrains.kotlinx.dataframe.io.readSqlQuery
|
||||
import org.jetbrains.kotlinx.dataframe.io.readSqlTable
|
||||
import org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.nio.file.Paths
|
||||
import java.sql.Connection
|
||||
import java.sql.DriverManager
|
||||
|
||||
public abstract class GenerateDataSchemaTask : DefaultTask() {
|
||||
|
||||
@get:Input
|
||||
public abstract val data: Property<Any>
|
||||
|
||||
@get:Input
|
||||
public abstract val csvOptions: Property<CsvOptionsDsl>
|
||||
|
||||
@get:Input
|
||||
public abstract val jsonOptions: Property<JsonOptionsDsl>
|
||||
|
||||
@get:Input
|
||||
public abstract val jdbcOptions: Property<JdbcOptionsDsl>
|
||||
|
||||
@get:Input
|
||||
public abstract val src: Property<File>
|
||||
|
||||
@get:Input
|
||||
public abstract val interfaceName: Property<String>
|
||||
|
||||
@get:Input
|
||||
public abstract val packageName: Property<String>
|
||||
|
||||
@get:Input
|
||||
public abstract val schemaVisibility: Property<DataSchemaVisibility>
|
||||
|
||||
@get:Input
|
||||
public abstract val defaultPath: Property<Boolean>
|
||||
|
||||
@get:Input
|
||||
public abstract val delimiters: SetProperty<Char>
|
||||
|
||||
@get:Input
|
||||
public abstract val enableExperimentalOpenApi: Property<Boolean>
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
@get:OutputFile
|
||||
public val dataSchema: Provider<File> = packageName.zip(interfaceName) { packageName, interfaceName ->
|
||||
val packagePath = packageName.replace('.', File.separatorChar)
|
||||
Paths.get(src.get().absolutePath, packagePath, "$interfaceName.Generated.kt").toFile()
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public fun generate() {
|
||||
val csvOptions = csvOptions.get()
|
||||
val jsonOptions = jsonOptions.get()
|
||||
val jdbcOptions = jdbcOptions.get()
|
||||
val schemaFile = dataSchema.get()
|
||||
val escapedPackageName = escapePackageName(packageName.get())
|
||||
|
||||
val rawUrl = data.get().toString()
|
||||
|
||||
// revisit architecture for an addition of the new data source https://github.com/Kotlin/dataframe/issues/450
|
||||
if (rawUrl.startsWith("jdbc")) {
|
||||
val connection = DriverManager.getConnection(rawUrl, jdbcOptions.user, jdbcOptions.password)
|
||||
connection.use {
|
||||
val schema = generateSchemaByJdbcOptions(jdbcOptions, connection)
|
||||
|
||||
val codeGenerator = CodeGenerator.create(useFqNames = false)
|
||||
|
||||
val additionalImports: List<String> = listOf()
|
||||
|
||||
val delimiters = delimiters.get()
|
||||
val codeGenResult = codeGenerator.generate(
|
||||
schema = schema,
|
||||
name = interfaceName.get(),
|
||||
fields = true,
|
||||
extensionProperties = false,
|
||||
isOpen = true,
|
||||
visibility = when (schemaVisibility.get()) {
|
||||
DataSchemaVisibility.INTERNAL -> MarkerVisibility.INTERNAL
|
||||
DataSchemaVisibility.IMPLICIT_PUBLIC -> MarkerVisibility.IMPLICIT_PUBLIC
|
||||
DataSchemaVisibility.EXPLICIT_PUBLIC -> MarkerVisibility.EXPLICIT_PUBLIC
|
||||
},
|
||||
readDfMethod = null,
|
||||
fieldNameNormalizer = NameNormalizer.from(delimiters),
|
||||
)
|
||||
|
||||
schemaFile.writeText(codeGenResult.toStandaloneSnippet(escapedPackageName, additionalImports))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
val url = urlOf(data.get())
|
||||
|
||||
val formats = listOfNotNull(
|
||||
CsvDeephaven(delimiter = csvOptions.delimiter),
|
||||
JSON(
|
||||
typeClashTactic = jsonOptions.typeClashTactic,
|
||||
keyValuePaths = jsonOptions.keyValuePaths,
|
||||
unifyNumbers = jsonOptions.unifyNumbers,
|
||||
),
|
||||
Excel(),
|
||||
TsvDeephaven(),
|
||||
ArrowFeather(),
|
||||
if (enableExperimentalOpenApi.get()) OpenApi() else null,
|
||||
)
|
||||
|
||||
// first try without creating dataframe
|
||||
when (val codeGenResult = CodeGenerator.urlCodeGenReader(url, interfaceName.get(), formats, false)) {
|
||||
is CodeGenerationReadResult.Success -> {
|
||||
val readDfMethod = codeGenResult.getReadDfMethod(stringOf(data.get()))
|
||||
val code = codeGenResult
|
||||
.code
|
||||
.toStandaloneSnippet(escapedPackageName, readDfMethod.additionalImports)
|
||||
|
||||
schemaFile.bufferedWriter().use {
|
||||
it.write(code)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
is CodeGenerationReadResult.Error ->
|
||||
logger.warn("Error while reading types-only from data at $url: ${codeGenResult.reason}")
|
||||
}
|
||||
|
||||
// on error, try with reading dataframe first
|
||||
val parsedDf = when (val readResult = CodeGenerator.urlDfReader(url, formats)) {
|
||||
is DfReadResult.Error -> throw Exception(
|
||||
"Error while reading dataframe from data at $url",
|
||||
readResult.reason,
|
||||
)
|
||||
|
||||
is DfReadResult.Success -> readResult
|
||||
}
|
||||
|
||||
val codeGenerator = CodeGenerator.create(useFqNames = false)
|
||||
val delimiters = delimiters.get()
|
||||
val readDfMethod = parsedDf.getReadDfMethod(stringOf(data.get()))
|
||||
val codeGenResult = codeGenerator.generate(
|
||||
schema = parsedDf.schema,
|
||||
name = interfaceName.get(),
|
||||
fields = true,
|
||||
extensionProperties = false,
|
||||
isOpen = true,
|
||||
visibility = when (schemaVisibility.get()) {
|
||||
DataSchemaVisibility.INTERNAL -> MarkerVisibility.INTERNAL
|
||||
DataSchemaVisibility.IMPLICIT_PUBLIC -> MarkerVisibility.IMPLICIT_PUBLIC
|
||||
DataSchemaVisibility.EXPLICIT_PUBLIC -> MarkerVisibility.EXPLICIT_PUBLIC
|
||||
},
|
||||
readDfMethod = readDfMethod,
|
||||
fieldNameNormalizer = NameNormalizer.from(delimiters),
|
||||
)
|
||||
schemaFile.writeText(codeGenResult.toStandaloneSnippet(escapedPackageName, readDfMethod.additionalImports))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: copy pasted from symbol-processor: DataSchemaGenerator, should be refactored somehow
|
||||
private fun generateSchemaByJdbcOptions(jdbcOptions: JdbcOptionsDsl, connection: Connection): DataFrameSchema {
|
||||
logger.debug("Table name: ${jdbcOptions.tableName}")
|
||||
logger.debug("SQL query: ${jdbcOptions.sqlQuery}")
|
||||
|
||||
val tableName = jdbcOptions.tableName
|
||||
val sqlQuery = jdbcOptions.sqlQuery
|
||||
|
||||
return when {
|
||||
isTableNameNotBlankAndQueryBlank(tableName, sqlQuery) -> generateSchemaForTable(connection, tableName)
|
||||
isQueryNotBlankAndTableBlank(tableName, sqlQuery) -> generateSchemaForQuery(connection, sqlQuery)
|
||||
areBothNotBlank(tableName, sqlQuery) -> throwBothFieldsFilledException(tableName, sqlQuery)
|
||||
else -> throwBothFieldsEmptyException(tableName, sqlQuery)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTableNameNotBlankAndQueryBlank(tableName: String, sqlQuery: String) =
|
||||
tableName.isNotBlank() && sqlQuery.isBlank()
|
||||
|
||||
private fun isQueryNotBlankAndTableBlank(tableName: String, sqlQuery: String) =
|
||||
sqlQuery.isNotBlank() && tableName.isBlank()
|
||||
|
||||
private fun areBothNotBlank(tableName: String, sqlQuery: String) = sqlQuery.isNotBlank() && tableName.isNotBlank()
|
||||
|
||||
private fun generateSchemaForTable(connection: Connection, tableName: String) =
|
||||
DataFrameSchema.readSqlTable(connection, tableName)
|
||||
|
||||
private fun generateSchemaForQuery(connection: Connection, sqlQuery: String) =
|
||||
DataFrameSchema.readSqlQuery(connection, sqlQuery)
|
||||
|
||||
private fun throwBothFieldsFilledException(tableName: String, sqlQuery: String): Nothing =
|
||||
throw RuntimeException(
|
||||
"Table name '$tableName' and SQL query '$sqlQuery' both are filled! " +
|
||||
"Clear 'tableName' or 'sqlQuery' properties in jdbcOptions with value to generate schema for SQL table or result of SQL query!",
|
||||
)
|
||||
|
||||
private fun throwBothFieldsEmptyException(tableName: String, sqlQuery: String): Nothing =
|
||||
throw RuntimeException(
|
||||
"Table name '$tableName' and SQL query '$sqlQuery' both are empty! " +
|
||||
"Populate 'tableName' or 'sqlQuery' properties in jdbcOptions with value to generate schema for SQL table or result of SQL query!",
|
||||
)
|
||||
|
||||
private fun stringOf(data: Any): String =
|
||||
when (data) {
|
||||
is File -> data.absolutePath
|
||||
|
||||
is URL -> data.toExternalForm()
|
||||
|
||||
is String ->
|
||||
when {
|
||||
isUrl(data) -> stringOf(URL(data))
|
||||
|
||||
else -> {
|
||||
val relativeFile = project.file(data)
|
||||
val absoluteFile = File(data)
|
||||
stringOf(if (relativeFile.exists()) relativeFile else absoluteFile)
|
||||
}
|
||||
}
|
||||
|
||||
else -> unsupportedType()
|
||||
}
|
||||
|
||||
// See RegexExpectationsTest
|
||||
private fun escapePackageName(packageName: String): String =
|
||||
if (packageName.isNotEmpty()) {
|
||||
packageName.split(NameChecker.PACKAGE_IDENTIFIER_DELIMITER)
|
||||
.joinToString(".") { part -> "`$part`" }
|
||||
} else {
|
||||
packageName
|
||||
}
|
||||
|
||||
private fun urlOf(data: Any): URL =
|
||||
when (data) {
|
||||
is File -> data.toURI()
|
||||
|
||||
is URL -> data.toURI()
|
||||
|
||||
is String -> when {
|
||||
isUrl(data) -> URL(data).toURI()
|
||||
|
||||
else -> {
|
||||
val relativeFile = project.file(data)
|
||||
val absoluteFile = File(data)
|
||||
|
||||
if (relativeFile.exists()) {
|
||||
relativeFile
|
||||
} else {
|
||||
absoluteFile
|
||||
}.toURI()
|
||||
}
|
||||
}
|
||||
|
||||
else -> unsupportedType()
|
||||
}.toURL()
|
||||
|
||||
private fun unsupportedType(): Nothing =
|
||||
throw IllegalArgumentException("data for schema \"${interfaceName.get()}\" must be File, URL or String")
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
internal object NameChecker {
|
||||
fun checkValidIdentifier(identifiers: String) {
|
||||
check(identifiers.none { char -> char in NAME_RESTRICTED_CHARS }) {
|
||||
val illegalChars = NAME_RESTRICTED_CHARS.intersect(identifiers.toSet()).joinToString(",")
|
||||
"$identifiers contains illegal characters: $illegalChars"
|
||||
}
|
||||
}
|
||||
|
||||
fun checkValidPackageName(name: String) {
|
||||
val identifiers = name
|
||||
.split(PACKAGE_IDENTIFIER_DELIMITER)
|
||||
.joinToString("")
|
||||
checkValidIdentifier(identifiers)
|
||||
}
|
||||
|
||||
// https://github.com/JetBrains/kotlin/blob/1.5.30/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmSimpleNameBacktickChecker.kt
|
||||
private val INVALID_CHARS = setOf('.', ';', '[', ']', '/', '<', '>', ':', '\\')
|
||||
private val DANGEROUS_CHARS = setOf('?', '*', '"', '|', '%')
|
||||
|
||||
// QuotedSymbol https://kotlinlang.org/spec/syntax-and-grammar.html#identifiers
|
||||
private val RESTRICTED_CHARS = setOf('`', '\r', '\n')
|
||||
private val NAME_RESTRICTED_CHARS = INVALID_CHARS + DANGEROUS_CHARS + RESTRICTED_CHARS
|
||||
|
||||
// https://kotlinlang.org/spec/syntax-and-grammar.html#grammar-rule-identifier
|
||||
val PACKAGE_IDENTIFIER_DELIMITER = "(\\n|\\r\\n)*\\.".toRegex()
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.Project
|
||||
import org.jetbrains.kotlinx.dataframe.api.JsonPath
|
||||
import org.jetbrains.kotlinx.dataframe.io.JSON
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
import java.net.URL
|
||||
|
||||
public open class SchemaGeneratorExtension {
|
||||
public lateinit var project: Project
|
||||
internal val schemas: MutableList<Schema> = mutableListOf()
|
||||
public var packageName: String? = null
|
||||
public var sourceSet: String? = null
|
||||
public var visibility: DataSchemaVisibility? = null
|
||||
internal var defaultPath: Boolean? = null
|
||||
internal var withNormalizationBy: Set<Char>? = null
|
||||
|
||||
/** Can be set to `true` to enable experimental OpenAPI 3.0.0 types support */
|
||||
public var enableExperimentalOpenApi: Boolean = false
|
||||
|
||||
public fun schema(config: Schema.() -> Unit) {
|
||||
val schema = Schema(project).apply(config)
|
||||
schemas.add(schema)
|
||||
}
|
||||
|
||||
public fun schema(config: Closure<*>) {
|
||||
val schema = Schema(project)
|
||||
project.configure(schema, config)
|
||||
schemas.add(schema)
|
||||
}
|
||||
|
||||
public fun withoutDefaultPath() {
|
||||
defaultPath = false
|
||||
}
|
||||
|
||||
public fun withNormalizationBy(vararg delimiter: Char) {
|
||||
withNormalizationBy = delimiter.toSet()
|
||||
}
|
||||
|
||||
// Overload for Groovy.
|
||||
// It's impossible to call a method with char argument without type cast in groovy, because it only has string literals
|
||||
public fun withNormalizationBy(delimiter: String) {
|
||||
withNormalizationBy = delimiter.toSet()
|
||||
}
|
||||
|
||||
public fun withoutNormalization() {
|
||||
withNormalizationBy = emptySet()
|
||||
}
|
||||
|
||||
public fun enableExperimentalOpenApi(enable: Boolean) {
|
||||
enableExperimentalOpenApi = enable
|
||||
}
|
||||
}
|
||||
|
||||
public class Schema(
|
||||
private val project: Project,
|
||||
public var data: Any? = null,
|
||||
public var src: File? = null,
|
||||
public var name: String? = null,
|
||||
public var packageName: String? = null,
|
||||
public var sourceSet: String? = null,
|
||||
public var visibility: DataSchemaVisibility? = null,
|
||||
internal var defaultPath: Boolean? = null,
|
||||
internal var withNormalizationBy: Set<Char>? = null,
|
||||
public val csvOptions: CsvOptionsDsl = CsvOptionsDsl(),
|
||||
public val jsonOptions: JsonOptionsDsl = JsonOptionsDsl(),
|
||||
public val jdbcOptions: JdbcOptionsDsl = JdbcOptionsDsl(),
|
||||
) {
|
||||
public fun setData(file: File) {
|
||||
data = file
|
||||
}
|
||||
|
||||
public fun setData(path: String) {
|
||||
data = path
|
||||
}
|
||||
|
||||
public fun setData(url: URL) {
|
||||
data = url
|
||||
}
|
||||
|
||||
public fun csvOptions(config: CsvOptionsDsl.() -> Unit) {
|
||||
csvOptions.apply(config)
|
||||
}
|
||||
|
||||
public fun csvOptions(config: Closure<*>) {
|
||||
project.configure(csvOptions, config)
|
||||
}
|
||||
|
||||
public fun jsonOptions(config: JsonOptionsDsl.() -> Unit) {
|
||||
jsonOptions.apply(config)
|
||||
}
|
||||
|
||||
public fun jsonOptions(config: Closure<*>) {
|
||||
project.configure(jsonOptions, config)
|
||||
}
|
||||
|
||||
public fun jdbcOptions(config: JdbcOptionsDsl.() -> Unit) {
|
||||
jdbcOptions.apply(config)
|
||||
}
|
||||
|
||||
public fun jdbcOptions(config: Closure<*>) {
|
||||
project.configure(jdbcOptions, config)
|
||||
}
|
||||
|
||||
public fun withoutDefaultPath() {
|
||||
defaultPath = false
|
||||
}
|
||||
|
||||
public fun withDefaultPath() {
|
||||
defaultPath = true
|
||||
}
|
||||
|
||||
public fun withNormalizationBy(vararg delimiter: Char) {
|
||||
withNormalizationBy = delimiter.toSet()
|
||||
}
|
||||
|
||||
// Overload for Groovy.
|
||||
// It's impossible to call a method with char argument without type cast in groovy, because it only has string literals
|
||||
public fun withNormalizationBy(delimiter: String) {
|
||||
withNormalizationBy = delimiter.toSet()
|
||||
}
|
||||
|
||||
public fun withoutNormalization() {
|
||||
withNormalizationBy = emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
// Without Serializable GradleRunner tests fail
|
||||
// TODO add more options
|
||||
public data class CsvOptionsDsl(var delimiter: Char = ',') : Serializable
|
||||
|
||||
public data class JsonOptionsDsl(
|
||||
var typeClashTactic: JSON.TypeClashTactic = JSON.TypeClashTactic.ARRAY_AND_VALUE_COLUMNS,
|
||||
var keyValuePaths: List<JsonPath> = emptyList(),
|
||||
var unifyNumbers: Boolean = true,
|
||||
) : Serializable
|
||||
|
||||
/**
|
||||
* Represents the configuration options for JDBC data source.
|
||||
*
|
||||
* @property [user] The username used to authenticate with the database. Default is an empty string.
|
||||
* @property [password] The password used to authenticate with the database. Default is an empty string.
|
||||
* @property [tableName] The name of the table to generate schema for. Default is an empty string.
|
||||
* @property [sqlQuery] The SQL query used to generate schema. Default is an empty string.
|
||||
*/
|
||||
public data class JdbcOptionsDsl(
|
||||
var user: String = "",
|
||||
var password: String = "",
|
||||
var tableName: String = "",
|
||||
var sqlQuery: String = "",
|
||||
) : Serializable
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import com.google.devtools.ksp.gradle.KspAATask
|
||||
import com.google.devtools.ksp.gradle.KspTask
|
||||
import com.google.devtools.ksp.gradle.KspTaskJvm
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.gradle.internal.logging.services.DefaultLoggingManager
|
||||
import org.gradle.kotlin.dsl.create
|
||||
import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.tasks.BaseKotlinCompile
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.Locale
|
||||
|
||||
public class SchemaGeneratorPlugin : Plugin<Project> {
|
||||
|
||||
override fun apply(target: Project) {
|
||||
val extension = target.extensions.create<SchemaGeneratorExtension>("dataframes")
|
||||
extension.project = target
|
||||
target.afterEvaluate {
|
||||
val appliedPlugin =
|
||||
KOTLIN_EXTENSIONS.firstNotNullOfOrNull {
|
||||
target.extensions.findByType(it.extensionClass)?.let { ext -> AppliedPlugin(ext, it) }
|
||||
}
|
||||
|
||||
if (appliedPlugin == null) {
|
||||
target.logger.warn("Schema generator plugin applied, but no Kotlin plugin was found")
|
||||
}
|
||||
|
||||
val generationTasks = extension.schemas.map {
|
||||
registerTask(target, extension, appliedPlugin, it)
|
||||
}
|
||||
val generateAll = target.tasks.register("generateDataFrames") {
|
||||
group = GROUP
|
||||
dependsOn(*generationTasks.toTypedArray())
|
||||
}
|
||||
tasks.withType(KspTask::class.java).configureEach {
|
||||
dependsOn(generateAll)
|
||||
dependsOn(*generationTasks.toTypedArray())
|
||||
}
|
||||
tasks.withType(KspAATask::class.java).configureEach {
|
||||
error(
|
||||
"Detected KSP2. This is not supported by the DataFrame Gradle/Ksp plugin. Add 'ksp.useKSP2=false' to 'gradle.properties'.",
|
||||
)
|
||||
}
|
||||
tasks.withType(BaseKotlinCompile::class.java).configureEach {
|
||||
dependsOn(generateAll)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerTask(
|
||||
target: Project,
|
||||
extension: SchemaGeneratorExtension,
|
||||
appliedPlugin: AppliedPlugin?,
|
||||
schema: Schema,
|
||||
): TaskProvider<GenerateDataSchemaTask> {
|
||||
val interfaceName = getInterfaceName(schema)
|
||||
|
||||
fun propertyError(property: String): Nothing {
|
||||
error(
|
||||
"No supported Kotlin plugin was found. Please apply one or specify property $property for schema $interfaceName explicitly",
|
||||
)
|
||||
}
|
||||
|
||||
val sourceSetName by lazy {
|
||||
schema.sourceSet
|
||||
?: extension.sourceSet
|
||||
?: (appliedPlugin ?: propertyError("sourceSet")).sourceSetConfiguration.defaultSourceSet
|
||||
}
|
||||
|
||||
val src: File = schema.src
|
||||
?: run {
|
||||
appliedPlugin ?: propertyError("src")
|
||||
val sourceSet = appliedPlugin.kotlinExtension.sourceSets.getByName(sourceSetName)
|
||||
val src = target.file(Paths.get("build/generated/dataframe/", sourceSetName, "kotlin").toFile())
|
||||
|
||||
// Add the new sources to the source set
|
||||
sourceSet.kotlin.srcDir(src)
|
||||
|
||||
// Configure the right ksp task to be aware of these new sources
|
||||
val kspTaskName = "ksp${sourceSetName.replaceFirstChar { it.uppercase() }}Kotlin"
|
||||
target.tasks.withType(KspTaskJvm::class.java).configureEach {
|
||||
if ((sourceSetName == "main" && name == "kspKotlin") || name == kspTaskName) {
|
||||
source(src)
|
||||
}
|
||||
}
|
||||
|
||||
src
|
||||
}
|
||||
|
||||
val packageName = schema.name?.let { name -> extractPackageName(name) }
|
||||
?: schema.packageName
|
||||
?: extension.packageName
|
||||
?: run {
|
||||
(appliedPlugin ?: propertyError("packageName"))
|
||||
val sourceSet = appliedPlugin.kotlinExtension.sourceSets.getByName(sourceSetName)
|
||||
val path = appliedPlugin.sourceSetConfiguration.getKotlinRoot(
|
||||
sourceSet.kotlin.sourceDirectories,
|
||||
sourceSetName,
|
||||
)
|
||||
val src = target.file(path)
|
||||
inferPackageName(src)
|
||||
}
|
||||
|
||||
val visibility = schema.visibility
|
||||
?: extension.visibility
|
||||
?: run {
|
||||
if (appliedPlugin != null) {
|
||||
when (appliedPlugin.kotlinExtension.explicitApi) {
|
||||
null -> DataSchemaVisibility.IMPLICIT_PUBLIC
|
||||
ExplicitApiMode.Strict -> DataSchemaVisibility.EXPLICIT_PUBLIC
|
||||
ExplicitApiMode.Warning -> DataSchemaVisibility.EXPLICIT_PUBLIC
|
||||
ExplicitApiMode.Disabled -> DataSchemaVisibility.IMPLICIT_PUBLIC
|
||||
}
|
||||
} else {
|
||||
DataSchemaVisibility.IMPLICIT_PUBLIC
|
||||
}
|
||||
}
|
||||
|
||||
val defaultPath = schema.defaultPath ?: extension.defaultPath ?: true
|
||||
val delimiters = schema.withNormalizationBy ?: extension.withNormalizationBy ?: setOf('\t', ' ', '_')
|
||||
|
||||
return target.tasks.register("generateDataFrame$interfaceName", GenerateDataSchemaTask::class.java) {
|
||||
(logging as? DefaultLoggingManager)?.setLevelInternal(LogLevel.QUIET)
|
||||
group = GROUP
|
||||
data.set(schema.data)
|
||||
this.interfaceName.set(interfaceName)
|
||||
this.packageName.set(packageName)
|
||||
this.src.set(src)
|
||||
this.schemaVisibility.set(visibility)
|
||||
this.csvOptions.set(schema.csvOptions)
|
||||
this.jsonOptions.set(schema.jsonOptions)
|
||||
this.jdbcOptions.set(schema.jdbcOptions)
|
||||
this.defaultPath.set(defaultPath)
|
||||
this.delimiters.set(delimiters)
|
||||
this.enableExperimentalOpenApi.set(extension.enableExperimentalOpenApi)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInterfaceName(schema: Schema): String? {
|
||||
val rawName = schema.name?.substringAfterLast('.')
|
||||
?: fileName(schema.data)
|
||||
?.toCamelCaseByDelimiters(delimiters)
|
||||
?.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
|
||||
?.removeSurrounding("`")
|
||||
?: return null
|
||||
NameChecker.checkValidIdentifier(rawName)
|
||||
return rawName
|
||||
}
|
||||
|
||||
private val delimiters = "[\\s_]".toRegex()
|
||||
|
||||
private class AppliedPlugin(
|
||||
val kotlinExtension: KotlinProjectExtension,
|
||||
val sourceSetConfiguration: SourceSetConfiguration<*>,
|
||||
)
|
||||
|
||||
private class SourceSetConfiguration<T : KotlinProjectExtension>(
|
||||
val extensionClass: Class<T>,
|
||||
val defaultSourceSet: String,
|
||||
) {
|
||||
fun getKotlinRoot(sourceDirectories: FileCollection, sourceSetName: String): File {
|
||||
fun sourceSet(lang: String) = Paths.get("src", sourceSetName, lang)
|
||||
val ktSet = sourceSet("kotlin")
|
||||
val javaSet = sourceSet("java")
|
||||
val isKotlinRoot: (Path) -> Boolean = { f -> f.endsWith(ktSet) }
|
||||
val genericRoot = sourceDirectories.find { isKotlinRoot(it.toPath()) }
|
||||
if (genericRoot != null) return genericRoot
|
||||
val androidSpecificRoot = if (extensionClass == KotlinAndroidProjectExtension::class.java) {
|
||||
val isAndroidKotlinRoot: (Path) -> Boolean = { f -> f.endsWith(javaSet) }
|
||||
sourceDirectories.find { isAndroidKotlinRoot(it.toPath()) }
|
||||
} else {
|
||||
error("Directory '$ktSet' was not found in $sourceSetName. Please, specify 'src' explicitly")
|
||||
}
|
||||
return androidSpecificRoot
|
||||
?: error(
|
||||
"Directory '$ktSet' or '$javaSet' was not found in $sourceSetName. Please, specify 'src' explicitly",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fileName(data: Any?): String? =
|
||||
when (data) {
|
||||
is String -> extractFileName(data)
|
||||
|
||||
is URL -> extractFileName(data)
|
||||
|
||||
is File -> extractFileName(data)
|
||||
|
||||
else -> throw IllegalArgumentException(
|
||||
"data for schema must be File, URL or String, but was ${data?.javaClass ?: ""}($data)",
|
||||
)
|
||||
}
|
||||
|
||||
private fun extractPackageName(fqName: String): String? {
|
||||
val packageName = fqName
|
||||
.substringBeforeLast('.')
|
||||
.takeIf { it != fqName }
|
||||
if (packageName != null) {
|
||||
NameChecker.checkValidPackageName(packageName)
|
||||
}
|
||||
return packageName
|
||||
}
|
||||
|
||||
private fun inferPackageName(root: File): String {
|
||||
val node = root.findDeepestCommonSubdirectory()
|
||||
val parentPath = root.absolutePath
|
||||
return node.absolutePath
|
||||
.removePrefix(parentPath)
|
||||
.removePrefix(File.separator)
|
||||
.replace(File.separatorChar, '.')
|
||||
.let {
|
||||
when {
|
||||
it.isEmpty() -> "dataframe"
|
||||
it.endsWith(".dataframe") -> it
|
||||
else -> "$it.dataframe"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val KOTLIN_EXTENSIONS = sequenceOf(
|
||||
SourceSetConfiguration(KotlinJvmProjectExtension::class.java, "main"),
|
||||
SourceSetConfiguration(KotlinMultiplatformExtension::class.java, "jvmMain"),
|
||||
SourceSetConfiguration(KotlinAndroidProjectExtension::class.java, "main"),
|
||||
)
|
||||
private const val GROUP = "dataframe"
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
public fun String.toCamelCaseByDelimiters(delimiters: Regex): String =
|
||||
split(delimiters)
|
||||
.joinToCamelCaseString()
|
||||
.replaceFirstChar { it.lowercase(Locale.getDefault()) }
|
||||
|
||||
public fun List<String>.joinToCamelCaseString(): String =
|
||||
joinToString(separator = "") { s ->
|
||||
s.replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
PREPROCESSOR_VERSION=%PREPROCESSOR_VERSION%
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import io.kotest.assertions.asClue
|
||||
import io.kotest.assertions.throwables.shouldNotThrowAny
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.assertions.throwables.shouldThrowAny
|
||||
import io.kotest.matchers.shouldBe
|
||||
import kotlinx.serialization.SerializationException
|
||||
import org.jetbrains.kotlinx.dataframe.DataFrame
|
||||
import org.jetbrains.kotlinx.dataframe.api.isEmpty
|
||||
import org.jetbrains.kotlinx.dataframe.io.read
|
||||
import org.jetbrains.kotlinx.dataframe.io.readCsv
|
||||
import org.jetbrains.kotlinx.dataframe.io.readSqlTable
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.sql.DriverManager
|
||||
import kotlin.io.path.absolutePathString
|
||||
|
||||
class DataFrameReadTest {
|
||||
@Test
|
||||
fun `file that does not exists`() {
|
||||
val temp = Files.createTempDirectory("").toFile()
|
||||
val definitelyDoesNotExists = File(temp, "absolutelyRandomName")
|
||||
shouldThrow<FileNotFoundException> {
|
||||
DataFrame.read(definitelyDoesNotExists)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `file with invalid json`() {
|
||||
val temp = Files.createTempDirectory("").toFile()
|
||||
val invalidJson = File(temp, "test.json").also { it.writeText(".") }
|
||||
shouldThrow<IllegalStateException> {
|
||||
DataFrame.read(invalidJson)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `file with invalid csv`() {
|
||||
val temp = Files.createTempDirectory("").toFile()
|
||||
val invalidCsv = File(temp, "test.csv").also { it.writeText("") }
|
||||
DataFrame.read(invalidCsv).isEmpty() shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invalid url`() {
|
||||
val exception = shouldThrowAny {
|
||||
DataFrame.read("http:://example.com")
|
||||
}
|
||||
exception.asClue {
|
||||
(exception is IllegalArgumentException || exception is IOException) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `valid url`() {
|
||||
useHostedJson("{}") {
|
||||
DataFrame.read(URL(it))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `path that is valid url`() {
|
||||
useHostedJson("{}") {
|
||||
DataFrame.read(it)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `URL with invalid JSON`() {
|
||||
useHostedJson("<invalid json>") { url ->
|
||||
shouldThrow<SerializationException> {
|
||||
DataFrame.read(url).also { println(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `data accessible and readable`() {
|
||||
shouldNotThrowAny {
|
||||
DataFrame.readCsv(Paths.get("../../data/jetbrains repositories.csv").absolutePathString(), skipLines = 1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `csvSample is valid csv`() {
|
||||
val temp = Files.createTempFile("f", "csv").toFile()
|
||||
temp.writeText(TestData.csvSample)
|
||||
|
||||
val df = DataFrame.read(temp)
|
||||
df.columnNames() shouldBe listOf("name", "age")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jdbcSample is valid jdbc`() {
|
||||
DriverManager.getConnection("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=MySQL;DATABASE_TO_UPPER=false")
|
||||
.use { connection ->
|
||||
// Create table Customer
|
||||
connection.createStatement().execute(
|
||||
"""
|
||||
CREATE TABLE Customer (
|
||||
id INT PRIMARY KEY,
|
||||
name VARCHAR(50),
|
||||
age INT
|
||||
)
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
// Create table Sale
|
||||
connection.createStatement().execute(
|
||||
"""
|
||||
CREATE TABLE Sale (
|
||||
id INT PRIMARY KEY,
|
||||
customerId INT,
|
||||
amount DECIMAL(10, 2)
|
||||
)
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
// add data to the Customer table
|
||||
connection.createStatement().execute("INSERT INTO Customer (id, name, age) VALUES (1, 'John', 40)")
|
||||
connection.createStatement().execute("INSERT INTO Customer (id, name, age) VALUES (2, 'Alice', 25)")
|
||||
connection.createStatement().execute("INSERT INTO Customer (id, name, age) VALUES (3, 'Bob', 47)")
|
||||
|
||||
// add data to the Sale table
|
||||
connection.createStatement().execute("INSERT INTO Sale (id, customerId, amount) VALUES (1, 1, 100.50)")
|
||||
connection.createStatement().execute("INSERT INTO Sale (id, customerId, amount) VALUES (2, 2, 50.00)")
|
||||
connection.createStatement().execute("INSERT INTO Sale (id, customerId, amount) VALUES (3, 1, 75.25)")
|
||||
connection.createStatement().execute("INSERT INTO Sale (id, customerId, amount) VALUES (4, 3, 35.15)")
|
||||
|
||||
val dfCustomer = DataFrame.readSqlTable(connection, "Customer")
|
||||
dfCustomer.columnNames() shouldBe listOf("id", "name", "age")
|
||||
|
||||
val dfSale = DataFrame.readSqlTable(connection, "Sale")
|
||||
dfSale.columnNames() shouldBe listOf("id", "customerId", "amount")
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.application.call
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.routing.get
|
||||
import io.ktor.server.routing.routing
|
||||
|
||||
fun useHostedJson(json: String, f: (url: String) -> Unit) {
|
||||
// duplicated in ksp/EmbeddedServerRunners.kt
|
||||
val port = 14771
|
||||
val server = embeddedServer(Netty, port = port) {
|
||||
routing {
|
||||
get("/test.json") {
|
||||
call.respondText(json, ContentType.Application.Json)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
f("http://0.0.0.0:$port/test.json")
|
||||
server.stop(500, 1000)
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
class ExtractFileNameTest {
|
||||
@Test
|
||||
fun `1`() {
|
||||
val name =
|
||||
extractFileName(
|
||||
"https://raw.githubusercontent.com/Kotlin/dataframe/8ea139c35aaf2247614bb227756d6fdba7359f6a/data/playlistItems.json",
|
||||
)
|
||||
assert(name == "playlistItems")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `2`() {
|
||||
val name =
|
||||
extractFileName(
|
||||
URL(
|
||||
"https://raw.githubusercontent.com/Kotlin/dataframe/8ea139c35aaf2247614bb227756d6fdba7359f6a/data/playlistItems.json",
|
||||
),
|
||||
)
|
||||
assert(name == "playlistItems")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `3`() {
|
||||
val name = extractFileName("/etc/example/file.json")
|
||||
assert(name == "file")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `4`() {
|
||||
val name = extractFileName(File("/etc/example/file.json"))
|
||||
assert(name == "file")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `5`() {
|
||||
val name = extractFileName("abc")
|
||||
assert(name == "abc")
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import io.kotest.matchers.should
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
|
||||
fun File.shouldEndWith(first: String, vararg path: String) =
|
||||
this should { it.endsWith(Paths.get(first, *path).toFile()) }
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
internal class FileTraversalTest {
|
||||
|
||||
private lateinit var temp: File
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
temp = Files.createTempDirectory("temp").toFile()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun directoryWithFileIsNotMiddlePackage() {
|
||||
val leaf = File(temp, "a/b/c").also { it.mkdirs() }
|
||||
val file = File(leaf, "test.txt").also { it.createNewFile() }
|
||||
leaf.isMiddlePackage() shouldBe false
|
||||
file.isMiddlePackage() shouldBe false
|
||||
listOf(leaf, file).filterNot { it.isMiddlePackage() }.first() shouldBe leaf
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emptySubdirectory() {
|
||||
val leaf = File(temp, "a/b/c").also { it.mkdirs() }
|
||||
temp.findDeepestCommonSubdirectory() shouldBe leaf
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subdirectoryWithFile() {
|
||||
val leaf = File(temp, "a/b/c").also { it.mkdirs() }
|
||||
File(leaf, "test.txt").also { it.createNewFile() }
|
||||
temp.findDeepestCommonSubdirectory() shouldBe leaf
|
||||
}
|
||||
|
||||
@Test
|
||||
fun forkAtDepth0() {
|
||||
File(temp, "a/b/c").also { it.mkdirs() }
|
||||
File(temp, "b/c/d").also { it.mkdirs() }
|
||||
temp.findDeepestCommonSubdirectory() shouldBe temp
|
||||
}
|
||||
|
||||
@Test
|
||||
fun forkAtDepth1() {
|
||||
val a = File(temp, "a").also { it.mkdirs() }
|
||||
File(a, "b/c").also { it.mkdirs() }
|
||||
File(a, "c/d").also { it.mkdirs() }
|
||||
temp.findDeepestCommonSubdirectory() shouldBe a
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noSubdirectories() {
|
||||
temp.findDeepestCommonSubdirectory() shouldBe temp
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import org.gradle.testkit.runner.BuildResult
|
||||
import org.gradle.testkit.runner.GradleRunner
|
||||
import org.intellij.lang.annotations.Language
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
fun runGradleBuild(
|
||||
task: String,
|
||||
@Language("kts") settingsGradle: (File) -> String = { "" },
|
||||
@Language("kts") build: (File) -> String,
|
||||
): Build {
|
||||
val buildDir = Files.createTempDirectory("test").toFile()
|
||||
val buildFile = File(buildDir, "build.gradle.kts")
|
||||
buildFile.writeText(build(buildDir))
|
||||
val settingsFile = File(buildDir, "settings.gradle.kts")
|
||||
settingsFile.writeText(settingsGradle(buildDir))
|
||||
val propertiesFile = File(buildDir, "gradle.properties")
|
||||
propertiesFile.writeText("ksp.useKSP2=false")
|
||||
return Build(buildDir, gradleRunner(buildDir, task).build())
|
||||
}
|
||||
|
||||
fun gradleRunner(buildDir: File, task: String): GradleRunner =
|
||||
GradleRunner.create()
|
||||
.withProjectDir(buildDir)
|
||||
// if we use api from the newest Gradle release, a user project will fail with NoSuchMethod
|
||||
// testing compatibility with an older Gradle version ensures our plugin can run on as many versions as possible
|
||||
.withGradleVersion("8.5")
|
||||
.withPluginClasspath()
|
||||
.withArguments(task, "--stacktrace", "--info")
|
||||
.withDebug(true)
|
||||
|
||||
data class Build(val buildDir: File, val buildResult: BuildResult)
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import org.gradle.api.internal.plugins.PluginApplicationException
|
||||
import org.junit.Test
|
||||
|
||||
class KotlinPluginExpectationsTest {
|
||||
|
||||
@Test
|
||||
fun `project can only apply 1 Kotlin plugin 1`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply("org.jetbrains.kotlin.jvm")
|
||||
shouldThrow<PluginApplicationException> {
|
||||
project.plugins.apply("org.jetbrains.kotlin.android")
|
||||
project.evaluate()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `project can only apply 1 Kotlin plugin 2`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply("org.jetbrains.kotlin.multiplatform")
|
||||
shouldThrow<PluginApplicationException> {
|
||||
project.plugins.apply("org.jetbrains.kotlin.android")
|
||||
project.evaluate()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `project can only apply 1 Kotlin plugin 3`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply("org.jetbrains.kotlin.multiplatform")
|
||||
shouldThrow<PluginApplicationException> {
|
||||
project.plugins.apply("org.jetbrains.kotlin.jvm")
|
||||
project.evaluate()
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.junit.Test
|
||||
|
||||
class RegexExpectationsTest {
|
||||
// for package name validation
|
||||
@Test
|
||||
fun `regex split returns non empty list for empty string`() {
|
||||
"".split("123".toRegex()) shouldBe listOf("")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `regex split ignore delimiter`() {
|
||||
"1.2.3".split("\\.".toRegex()) shouldBe listOf("1", "2", "3")
|
||||
}
|
||||
}
|
||||
+413
@@ -0,0 +1,413 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import io.kotest.assertions.asClue
|
||||
import io.kotest.inspectors.forOne
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import org.gradle.testkit.runner.TaskOutcome
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
internal class SchemaGeneratorPluginTest {
|
||||
|
||||
private companion object {
|
||||
private val KOTLIN_VERSION = TestData.kotlinVersion
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `plugin configured via configure`() {
|
||||
val (_, result) = runGradleBuild(":generateDataFrameTest") {
|
||||
// language=kts
|
||||
"""
|
||||
import java.net.URI
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$KOTLIN_VERSION"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
configure<SchemaGeneratorExtension> {
|
||||
schema {
|
||||
data = URI("https://raw.githubusercontent.com/Kotlin/dataframe/8ea139c35aaf2247614bb227756d6fdba7359f6a/data/playlistItems.json").toURL()
|
||||
name = "Test"
|
||||
packageName = "org.test"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":generateDataFrameTest")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `plugin configured via extension DSL`() {
|
||||
val (_, result) = runGradleBuild(":generateDataFrameTest") {
|
||||
// language=kts
|
||||
"""
|
||||
import java.net.URI
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$KOTLIN_VERSION"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = URI("https://raw.githubusercontent.com/Kotlin/dataframe/8ea139c35aaf2247614bb227756d6fdba7359f6a/data/playlistItems.json").toURL()
|
||||
name = "Test"
|
||||
packageName = "org.test"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":generateDataFrameTest")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `plugin configured via extension DSL with Groovy`() {
|
||||
val buildDir = Files.createTempDirectory("test").toFile()
|
||||
val buildFile = File(buildDir, "build.gradle")
|
||||
buildFile.writeText(
|
||||
// language=groovy
|
||||
"""
|
||||
import java.net.URI
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm" version "$KOTLIN_VERSION"
|
||||
id "org.jetbrains.kotlinx.dataframe"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = new URI("https://raw.githubusercontent.com/Kotlin/dataframe/8ea139c35aaf2247614bb227756d6fdba7359f6a/data/playlistItems.json").toURL()
|
||||
name = "Test"
|
||||
packageName = "org.test"
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
val result = gradleRunner(buildDir, ":generateDataFrameTest").build()
|
||||
result.task(":generateDataFrameTest")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `delimiters configured with Groovy`() {
|
||||
val buildDir = Files.createTempDirectory("test").toFile()
|
||||
val buildFile = File(buildDir, "build.gradle")
|
||||
buildFile.writeText(
|
||||
// language=groovy
|
||||
"""
|
||||
import java.net.URL
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm" version "$KOTLIN_VERSION"
|
||||
id "org.jetbrains.kotlinx.dataframe"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = new URI("https://raw.githubusercontent.com/Kotlin/dataframe/8ea139c35aaf2247614bb227756d6fdba7359f6a/data/playlistItems.json").toURL()
|
||||
name = "Test"
|
||||
packageName = "org.test"
|
||||
withNormalizationBy('-_\t ')
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
val result = gradleRunner(buildDir, ":generateDataFrameTest").build()
|
||||
result.task(":generateDataFrameTest")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `plugin configure multiple schemas from URLs via extension`() {
|
||||
val (_, result) = runGradleBuild(":generateDataFrames") {
|
||||
// language=kts
|
||||
"""
|
||||
import java.net.URI
|
||||
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$KOTLIN_VERSION"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = URI("https://raw.githubusercontent.com/Kotlin/dataframe/8ea139c35aaf2247614bb227756d6fdba7359f6a/data/playlistItems.json").toURL()
|
||||
name = "Test"
|
||||
packageName = "org.test"
|
||||
}
|
||||
schema {
|
||||
data = URI("https://raw.githubusercontent.com/Kotlin/dataframe/master/data/jetbrains_repositories.csv").toURL()
|
||||
name = "Schema"
|
||||
packageName = "org.test"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":generateDataFrameTest")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
result.task(":generateDataFrameSchema")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `plugin configure multiple schemas from strings via extension`() {
|
||||
val (_, result) = runGradleBuild(":generateDataFrames") { buildDir ->
|
||||
File(buildDir, "data").also {
|
||||
it.mkdirs()
|
||||
File(it, TestData.csvName).writeText(TestData.csvSample)
|
||||
File(it, TestData.jsonName).writeText(TestData.jsonSample)
|
||||
}
|
||||
// language=kts
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$KOTLIN_VERSION"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = "data/${TestData.csvName}"
|
||||
name = "Test"
|
||||
packageName = "org.test"
|
||||
}
|
||||
schema {
|
||||
data = "data/${TestData.jsonName}"
|
||||
name = "Schema"
|
||||
packageName = "org.test"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":generateDataFrameTest")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
result.task(":generateDataFrameSchema")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `data is string and relative path`() {
|
||||
val (_, result) = runGradleBuild(":generateDataFrameTest") { buildDir ->
|
||||
val dataDir = File(buildDir, "data").also { it.mkdirs() }
|
||||
File(dataDir, TestData.jsonName).writeText(TestData.jsonSample)
|
||||
// language=kts
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$KOTLIN_VERSION"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = "data/${TestData.jsonName}"
|
||||
name = "Test"
|
||||
packageName = "org.test"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":generateDataFrameTest")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `data is string and absolute path`() {
|
||||
val (_, result) = runGradleBuild(":generateDataFrameTest") { buildDir ->
|
||||
val dataDir = File(buildDir, "data").also { it.mkdirs() }
|
||||
val file = File(dataDir, TestData.jsonName).also { it.writeText(TestData.jsonSample) }
|
||||
val absolutePath = file.absolutePath.replace(File.separatorChar, '/')
|
||||
// language=kts
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$KOTLIN_VERSION"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = "$absolutePath"
|
||||
name = "Test"
|
||||
packageName = "org.test"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":generateDataFrameTest")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `data is string and url`() {
|
||||
val (_, result) = runGradleBuild(":generateDataFrameTest") { buildDir ->
|
||||
println("Build dir: $buildDir")
|
||||
// language=kts
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$KOTLIN_VERSION"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = "https://raw.githubusercontent.com/Kotlin/dataframe/8ea139c35aaf2247614bb227756d6fdba7359f6a/data/ghost.json"
|
||||
name = "Test"
|
||||
packageName = "org.test"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":generateDataFrameTest")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `data is OpenApi string and url`() {
|
||||
val (_, result) = runGradleBuild(":generateDataFrameTest") { buildDir ->
|
||||
println("Build dir: $buildDir")
|
||||
// language=kts
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$KOTLIN_VERSION"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = "https://raw.githubusercontent.com/Kotlin/dataframe/2ad1c2f5d27267fa9b2abde1bf611fa50c5abce9/data/petstore.json"
|
||||
name = "Test"
|
||||
packageName = "org.test"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":generateDataFrameTest")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `custom csv delimiter`() {
|
||||
val (buildDir, result) = runGradleBuild(":generateDataFrameTest") { buildDir ->
|
||||
val csv = "semicolons.csv"
|
||||
val data = File(buildDir, csv)
|
||||
data.writeText(
|
||||
"""
|
||||
a;b;c
|
||||
1;2;3
|
||||
""".trimIndent(),
|
||||
)
|
||||
// language=kts
|
||||
"""
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "$KOTLIN_VERSION"
|
||||
id("org.jetbrains.kotlinx.dataframe")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dataframes {
|
||||
schema {
|
||||
data = "$csv"
|
||||
name = "Test"
|
||||
packageName = "org.test"
|
||||
csvOptions {
|
||||
delimiter = ';'
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
result.task(":generateDataFrameTest")?.outcome shouldBe TaskOutcome.SUCCESS
|
||||
File(buildDir, "build/generated/dataframe/main/kotlin/org/test/Test.Generated.kt").asClue {
|
||||
it.readLines().let {
|
||||
it.forOne {
|
||||
it.shouldContain("val a")
|
||||
}
|
||||
it.forOne {
|
||||
it.shouldContain("val b")
|
||||
}
|
||||
it.forOne {
|
||||
it.shouldContain("val c")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `most specific sourceSet is used in the packageName inference`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.plugins.apply("org.jetbrains.kotlin.jvm")
|
||||
project.extensions.getByType(KotlinJvmProjectExtension::class.java).apply {
|
||||
sourceSets.create("main1")
|
||||
}
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
sourceSet = "main"
|
||||
schema {
|
||||
sourceSet = "main1"
|
||||
data = "123"
|
||||
name = "321"
|
||||
}
|
||||
}
|
||||
project.file("src/main1/kotlin/org/example/test").also { it.mkdirs() }
|
||||
project.evaluate()
|
||||
(project.tasks.getByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.dataSchema
|
||||
.get()
|
||||
.shouldBe(
|
||||
project.file("build/generated/dataframe/main1/kotlin/org/example/test/dataframe/321.Generated.kt"),
|
||||
)
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import io.kotest.inspectors.forAny
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
import org.junit.Test
|
||||
|
||||
class SourceSetsExpectationsTest {
|
||||
@Test
|
||||
fun `there is main in default JVM project`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply("org.jetbrains.kotlin.jvm")
|
||||
project.extensions.getByType(KotlinJvmProjectExtension::class.java).let { extension ->
|
||||
val main = extension.sourceSets.getByName("main")
|
||||
main.kotlin.sourceDirectories.toList().forAny {
|
||||
it.shouldEndWith("src", "main", "kotlin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `there is no jvmMain in default Multiplatform project`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply("org.jetbrains.kotlin.multiplatform")
|
||||
project.extensions.getByType(KotlinMultiplatformExtension::class.java).let {
|
||||
it.sourceSets.findByName("jvmMain") shouldBe null
|
||||
}
|
||||
}
|
||||
}
|
||||
dataframe/plugins/dataframe-gradle-plugin/src/test/kotlin/org/jetbrains/dataframe/gradle/TestData.kt
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import org.jetbrains.kotlinx.dataframe.BuildConfig
|
||||
|
||||
object TestData {
|
||||
|
||||
val csvSample =
|
||||
"""
|
||||
name, age
|
||||
Alice, 15
|
||||
Bob,
|
||||
""".trimIndent()
|
||||
|
||||
val csvName = "data.csv"
|
||||
|
||||
val jsonSample = """{"name": "Test"}"""
|
||||
|
||||
val jsonName = "test.json"
|
||||
|
||||
val kotlinVersion = BuildConfig.KOTLIN_VERSION
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
package org.jetbrains.dataframe.gradle
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.internal.project.DefaultProject
|
||||
import org.gradle.api.internal.project.ProjectInternal
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.build.event.BuildEventsListenerRegistry
|
||||
import org.gradle.internal.service.DefaultServiceRegistry
|
||||
import org.gradle.internal.service.scopes.Scope
|
||||
import org.gradle.internal.service.scopes.ServiceScope
|
||||
import org.gradle.testfixtures.ProjectBuilder
|
||||
import org.gradle.tooling.events.OperationCompletionListener
|
||||
import java.lang.reflect.Field
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
internal fun makeProject(): ProjectInternal {
|
||||
val project = ProjectBuilder.builder().build() as ProjectInternal
|
||||
addBuildEventsListenerRegistryMock(project)
|
||||
return project
|
||||
}
|
||||
|
||||
/**
|
||||
* In Gradle 6.7-rc-1 BuildEventsListenerRegistry service is not created in we need it in order
|
||||
* to instantiate AGP. This creates a fake one and injects it - http://b/168630734.
|
||||
*/
|
||||
internal fun addBuildEventsListenerRegistryMock(project: Project) {
|
||||
try {
|
||||
val projectScopeServices = (project as DefaultProject).services as DefaultServiceRegistry
|
||||
val state: Field = DefaultServiceRegistry::class.java.getDeclaredField("state")
|
||||
state.isAccessible = true
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val stateValue: AtomicReference<Any> = state.get(projectScopeServices) as AtomicReference<Any>
|
||||
val enumClass = Class.forName(DefaultServiceRegistry::class.java.name + "\$State")
|
||||
stateValue.set(enumClass.enumConstants[0])
|
||||
|
||||
// add service and set state so that future mutations are not allowed
|
||||
projectScopeServices.add(BuildEventsListenerRegistryMock::class.java, BuildEventsListenerRegistryMock)
|
||||
stateValue.set(enumClass.enumConstants[1])
|
||||
} catch (e: Throwable) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceScope(Scope.Project::class)
|
||||
object BuildEventsListenerRegistryMock : BuildEventsListenerRegistry {
|
||||
override fun onTaskCompletion(listener: Provider<out OperationCompletionListener>?) = Unit
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package org.jetbrains.dataframe.gradle.taskProperties
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.jetbrains.dataframe.gradle.GenerateDataSchemaTask
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorPlugin
|
||||
import org.jetbrains.dataframe.gradle.makeProject
|
||||
import org.junit.Test
|
||||
|
||||
class TaskCsvOptionsPropertyTest {
|
||||
@Test
|
||||
fun `configure delimiter`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
val tab = '\t'
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
schema {
|
||||
data = "/test/data.csv"
|
||||
name = "org.example.Data"
|
||||
src = project.projectDir
|
||||
csvOptions {
|
||||
delimiter = tab
|
||||
}
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
(project.tasks.getByName("generateDataFrameData") as GenerateDataSchemaTask)
|
||||
.csvOptions.get()
|
||||
.delimiter shouldBe tab
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package org.jetbrains.dataframe.gradle.taskProperties
|
||||
|
||||
import io.kotest.assertions.throwables.shouldNotThrow
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.gradle.api.ProjectConfigurationException
|
||||
import org.jetbrains.dataframe.gradle.GenerateDataSchemaTask
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorPlugin
|
||||
import org.jetbrains.dataframe.gradle.makeProject
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
||||
import org.junit.Test
|
||||
|
||||
class TaskDataSchemaPropertyTest {
|
||||
@Test
|
||||
fun `extension sourceSet present in project`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.plugins.apply("org.jetbrains.kotlin.jvm")
|
||||
project.extensions.getByType(KotlinJvmProjectExtension::class.java).apply {
|
||||
sourceSets.create("main1")
|
||||
}
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
sourceSet = "main1"
|
||||
schema {
|
||||
data = "123"
|
||||
name = "org.example.my.321"
|
||||
}
|
||||
}
|
||||
shouldNotThrow<ProjectConfigurationException> {
|
||||
project.evaluate()
|
||||
}
|
||||
(project.tasks.getByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.dataSchema.get()
|
||||
.shouldBe(project.file("build/generated/dataframe/main1/kotlin/org/example/my/321.Generated.kt"))
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package org.jetbrains.dataframe.gradle.taskProperties
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import org.gradle.api.ProjectConfigurationException
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorPlugin
|
||||
import org.jetbrains.dataframe.gradle.makeProject
|
||||
import org.junit.Test
|
||||
|
||||
class TaskNamePropertyTest {
|
||||
@Test
|
||||
fun `task name is last part of FQ name`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
schema {
|
||||
data = "123"
|
||||
name = "org.example.my.321"
|
||||
src = project.projectDir
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
(project.tasks.findByName("generateDataFrame321") shouldNotBe null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `name from task property have higher priority then inferred from data`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
schema {
|
||||
data = "/test/data.json"
|
||||
name = "org.example.my.321"
|
||||
src = project.projectDir
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
(project.tasks.findByName("generateDataFrame321") shouldNotBe null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `task name contains invalid characters`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
schema {
|
||||
data = "123"
|
||||
name = "org.test.example.[321]"
|
||||
src = project.projectDir
|
||||
}
|
||||
}
|
||||
val exception = shouldThrow<ProjectConfigurationException> {
|
||||
project.evaluate()
|
||||
}
|
||||
exception.causes.single().message shouldContain "[321] contains illegal characters: [,]"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `data name should not override invalid name`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
schema {
|
||||
data =
|
||||
"https://datalore-samples.s3-eu-west-1.amazonaws.com/datalore_gallery_of_samples/city_population.csv"
|
||||
name = "org.test.example.[321]"
|
||||
src = project.projectDir
|
||||
}
|
||||
}
|
||||
val exception = shouldThrow<ProjectConfigurationException> {
|
||||
project.evaluate()
|
||||
}
|
||||
exception.causes.single().message shouldContain "[321] contains illegal characters: [,]"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `name convention is data file name`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
schema {
|
||||
data =
|
||||
"https://datalore-samples.s3-eu-west-1.amazonaws.com/datalore_gallery_of_samples/city_population.csv"
|
||||
packageName = ""
|
||||
src = project.projectDir
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
project.tasks.getByName("generateDataFrameCityPopulation") shouldNotBe null
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
package org.jetbrains.dataframe.gradle.taskProperties
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.gradle.api.ProjectConfigurationException
|
||||
import org.jetbrains.dataframe.gradle.GenerateDataSchemaTask
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorPlugin
|
||||
import org.jetbrains.dataframe.gradle.makeProject
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
class TaskPackageNamePropertyTest {
|
||||
@Test
|
||||
fun `task inherit default packageName from extension`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
packageName = "org.example.test"
|
||||
schema {
|
||||
data = "123"
|
||||
name = "321"
|
||||
src = project.projectDir
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
(project.tasks.getByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.packageName.get() shouldBe "org.example.test"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `task packageName overrides packageName from extension`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
packageName = "org.example.test"
|
||||
schema {
|
||||
data = "123"
|
||||
packageName = "org.example.my"
|
||||
name = "321"
|
||||
src = project.projectDir
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
(project.tasks.getByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.packageName.get() shouldBe "org.example.my"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `task packageName convention is package part of FQ name`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
packageName = "org.example.test"
|
||||
schema {
|
||||
data = "123"
|
||||
name = "org.example.my.321"
|
||||
src = project.projectDir
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
(project.tasks.findByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.packageName.get() shouldBe "org.example.my"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `name package part overrides packageName`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
schema {
|
||||
data = "123"
|
||||
packageName = "org.example.test"
|
||||
name = "org.example.my.321"
|
||||
src = project.projectDir
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
(project.tasks.findByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.packageName.get() shouldBe "org.example.my"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `illegal characters in package part of name cause exception`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
schema {
|
||||
data = "123"
|
||||
name = "`[org]`.321"
|
||||
}
|
||||
}
|
||||
shouldThrow<ProjectConfigurationException> {
|
||||
project.evaluate()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `task infers packageName from directory structure`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.plugins.apply("org.jetbrains.kotlin.jvm")
|
||||
File(project.projectDir, "/src/main/kotlin/org/test/").also { it.mkdirs() }
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
schema {
|
||||
data = "123"
|
||||
name = "321"
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
(project.tasks.getByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.packageName.get() shouldBe "org.test.dataframe"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `task will not add _dataframe_ if inferred package ends with _dataframe_`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.plugins.apply("org.jetbrains.kotlin.jvm")
|
||||
File(project.projectDir, "/src/main/kotlin/org/dataframe/").also { it.mkdirs() }
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
schema {
|
||||
data = "123"
|
||||
name = "321"
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
(project.tasks.getByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.packageName.get() shouldBe "org.dataframe"
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package org.jetbrains.dataframe.gradle.taskProperties
|
||||
|
||||
import io.kotest.assertions.throwables.shouldNotThrow
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.inspectors.forOne
|
||||
import io.kotest.matchers.collections.shouldHaveSize
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import org.gradle.api.ProjectConfigurationException
|
||||
import org.jetbrains.dataframe.gradle.GenerateDataSchemaTask
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorPlugin
|
||||
import org.jetbrains.dataframe.gradle.makeProject
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
||||
import org.junit.Test
|
||||
|
||||
class TaskSourceSetPropertyTest {
|
||||
@Test
|
||||
fun `extension sourceSet present in project`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.plugins.apply("org.jetbrains.kotlin.jvm")
|
||||
project.extensions.getByType(KotlinJvmProjectExtension::class.java).apply {
|
||||
sourceSets.create("main1")
|
||||
}
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
sourceSet = "main1"
|
||||
schema {
|
||||
data = "123"
|
||||
name = "org.example.my.321"
|
||||
}
|
||||
}
|
||||
shouldNotThrow<ProjectConfigurationException> {
|
||||
project.evaluate()
|
||||
}
|
||||
(project.tasks.getByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.src.get()
|
||||
.shouldBe(project.file("build/generated/dataframe/main1/kotlin/"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension sourceSet not present in project`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.plugins.apply("org.jetbrains.kotlin.jvm")
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
sourceSet = "main1"
|
||||
schema {
|
||||
data = "123"
|
||||
name = "org.example.my.321"
|
||||
}
|
||||
}
|
||||
val exception = shouldThrow<ProjectConfigurationException> {
|
||||
project.evaluate()
|
||||
}
|
||||
exception.causes.shouldHaveSize(1)
|
||||
exception.causes.forOne { it.message shouldContain "KotlinSourceSet with name 'main1' not found" }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension sourceSet not specified`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.plugins.apply("org.jetbrains.kotlin.jvm")
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
schema {
|
||||
data = "123"
|
||||
name = "org.example.my.321"
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
(project.tasks.getByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.src.get()
|
||||
.shouldBe(project.file("build/generated/dataframe/main/kotlin/"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `choose most specific sourceSet`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.plugins.apply("org.jetbrains.kotlin.jvm")
|
||||
project.extensions.getByType(KotlinJvmProjectExtension::class.java).apply {
|
||||
sourceSets.create("main1")
|
||||
}
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
sourceSet = "main"
|
||||
schema {
|
||||
sourceSet = "main1"
|
||||
data = "123"
|
||||
name = "321"
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
(project.tasks.getByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.src.get()
|
||||
.shouldBe(project.file("build/generated/dataframe/main1/kotlin/"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension sourceSet specified but no kotlin plugin found`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
packageName = "org.example.test"
|
||||
sourceSet = "myMain"
|
||||
schema {
|
||||
data = "123"
|
||||
name = "org.example.my.321"
|
||||
}
|
||||
}
|
||||
val exception = shouldThrow<ProjectConfigurationException> {
|
||||
project.evaluate()
|
||||
}
|
||||
exception.causes.shouldHaveSize(1)
|
||||
exception.causes.forOne {
|
||||
it.message shouldContain
|
||||
"No supported Kotlin plugin was found. Please apply one or specify property src for schema 321 explicitly"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `task with explicit src don't evaluates sourceSet`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
packageName = "org.example.test"
|
||||
sourceSet = "myMain"
|
||||
schema {
|
||||
data = "123"
|
||||
src = project.file("src/main/kotlin")
|
||||
name = "org.example.my.321"
|
||||
}
|
||||
}
|
||||
shouldNotThrow<ProjectConfigurationException> {
|
||||
project.evaluate()
|
||||
}
|
||||
(project.tasks.getByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.dataSchema.get()
|
||||
.shouldBe(project.file("src/main/kotlin/org/example/my/321.Generated.kt"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `task and extension sourceSet not present in project`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
packageName = "org.example.test"
|
||||
sourceSet = "myMain"
|
||||
schema {
|
||||
data = "123"
|
||||
sourceSet = "myMain1"
|
||||
name = "org.example.my.321"
|
||||
}
|
||||
}
|
||||
val exception = shouldThrow<ProjectConfigurationException> {
|
||||
project.evaluate()
|
||||
}
|
||||
exception.causes.shouldHaveSize(1)
|
||||
exception.causes.forOne {
|
||||
it.message shouldContain
|
||||
"No supported Kotlin plugin was found. Please apply one or specify property src for schema 321 explicitly"
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package org.jetbrains.dataframe.gradle.taskProperties
|
||||
|
||||
import io.kotest.assertions.throwables.shouldNotThrow
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.gradle.api.ProjectConfigurationException
|
||||
import org.jetbrains.dataframe.gradle.DataSchemaVisibility
|
||||
import org.jetbrains.dataframe.gradle.GenerateDataSchemaTask
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorExtension
|
||||
import org.jetbrains.dataframe.gradle.SchemaGeneratorPlugin
|
||||
import org.jetbrains.dataframe.gradle.makeProject
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
||||
import org.junit.Test
|
||||
|
||||
class TaskVisibilityPropertyTest {
|
||||
@Test
|
||||
fun `extension sourceSet present in project 1 `() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
sourceSet = "main1"
|
||||
visibility = DataSchemaVisibility.INTERNAL
|
||||
schema {
|
||||
src = project.file("src")
|
||||
data = "123"
|
||||
name = "org.example.my.321"
|
||||
}
|
||||
}
|
||||
shouldNotThrow<ProjectConfigurationException> {
|
||||
project.evaluate()
|
||||
}
|
||||
(project.tasks.getByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.schemaVisibility.get()
|
||||
.shouldBe(DataSchemaVisibility.INTERNAL)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension sourceSet present in project 2`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
sourceSet = "main1"
|
||||
visibility = DataSchemaVisibility.INTERNAL
|
||||
schema {
|
||||
src = project.file("src")
|
||||
visibility = DataSchemaVisibility.EXPLICIT_PUBLIC
|
||||
data = "123"
|
||||
name = "org.example.my.321"
|
||||
}
|
||||
}
|
||||
shouldNotThrow<ProjectConfigurationException> {
|
||||
project.evaluate()
|
||||
}
|
||||
(project.tasks.getByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.schemaVisibility.get()
|
||||
.shouldBe(DataSchemaVisibility.EXPLICIT_PUBLIC)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension sourceSet present in project`() {
|
||||
val project = makeProject()
|
||||
project.plugins.apply(SchemaGeneratorPlugin::class.java)
|
||||
project.plugins.apply("org.jetbrains.kotlin.jvm")
|
||||
project.extensions.getByType(KotlinJvmProjectExtension::class.java).apply {
|
||||
sourceSets.create("main1")
|
||||
explicitApi()
|
||||
}
|
||||
project.extensions.getByType(SchemaGeneratorExtension::class.java).apply {
|
||||
sourceSet = "main1"
|
||||
schema {
|
||||
data = "123"
|
||||
name = "org.example.my.321"
|
||||
}
|
||||
}
|
||||
shouldNotThrow<ProjectConfigurationException> {
|
||||
project.evaluate()
|
||||
}
|
||||
(project.tasks.getByName("generateDataFrame321") as GenerateDataSchemaTask)
|
||||
.schemaVisibility.get()
|
||||
.shouldBe(DataSchemaVisibility.EXPLICIT_PUBLIC)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user