From f382e6e6ab40880a5a5c16705627366c60136232 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim <98384+nafg@users.noreply.github.com> Date: Thu, 7 Dec 2023 03:38:19 -0500 Subject: [PATCH] codegen: test that output compiles --- .github/workflows/ci.yml | 8 +- .scalafmt.conf | 4 + build.sbt | 15 +++ ci.sbt | 18 ++- .../EntityTableModulesCodeGenerator.scala | 2 + .../codegen/TablesCodeGenerator.scala | 4 +- .../src/test/resources/config.conf | 3 + .../test/resources/{ => entity}/Models.scala | 2 +- .../resources/{ => entity}/TableModules.scala | 6 +- .../src/test/resources/entity/package.scala | 10 ++ .../src/test/resources/init.sql | 30 +++++ .../src/test/resources/plain/Models.scala | 28 +++++ .../test/resources/{ => plain}/Tables.scala | 9 +- .../slick/additions/codegen/CodeGen.scala | 9 ++ .../additions/codegen/CodeGenTests.scala | 14 +++ .../additions/codegen/CodeGeneration.scala | 26 ++++ .../additions/codegen/CodegenTests.scala | 111 ------------------ .../scala/slick/additions/codegen/Util.scala | 37 ++++++ 18 files changed, 213 insertions(+), 123 deletions(-) create mode 100644 slick-additions-codegen/src/test/resources/config.conf rename slick-additions-codegen/src/test/resources/{ => entity}/Models.scala (97%) rename slick-additions-codegen/src/test/resources/{ => entity}/TableModules.scala (96%) create mode 100644 slick-additions-codegen/src/test/resources/entity/package.scala create mode 100644 slick-additions-codegen/src/test/resources/init.sql create mode 100644 slick-additions-codegen/src/test/resources/plain/Models.scala rename slick-additions-codegen/src/test/resources/{ => plain}/Tables.scala (94%) create mode 100644 slick-additions-codegen/src/test/scala/slick/additions/codegen/CodeGen.scala create mode 100644 slick-additions-codegen/src/test/scala/slick/additions/codegen/CodeGenTests.scala create mode 100644 slick-additions-codegen/src/test/scala/slick/additions/codegen/CodeGeneration.scala delete mode 100644 slick-additions-codegen/src/test/scala/slick/additions/codegen/CodegenTests.scala create mode 100644 slick-additions-codegen/src/test/scala/slick/additions/codegen/Util.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e006ac..4239948 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,8 +57,14 @@ jobs: - name: Build project run: sbt ++${{ matrix.scala }} test + - name: Check that codegen output compiles + run: sbt ++${{ matrix.scala }} 'slick-additions-codegen/Test/runMain slick.additions.codegen.CodeGen' test-codegen/compile + + - name: Check that codegen output hasn't changed + run: git diff --exit-code --quiet HEAD slick-additions-codegen/src/test/resources + - name: Compress target directories - run: tar cf targets.tar target slick-additions-entity/.jvm/target slick-additions-entity/.js/target slick-additions-codegen/target project/target + run: tar cf targets.tar target slick-additions-codegen/src/test/resources/target slick-additions-entity/.jvm/target slick-additions-entity/.js/target slick-additions-codegen/target project/target - name: Upload target directories uses: actions/upload-artifact@v2 diff --git a/.scalafmt.conf b/.scalafmt.conf index d0ec960..2df196c 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,6 +1,10 @@ version = "3.7.17" runner.dialect = scala213source3 +project.excludePaths = [ + "glob:**/slick-additions-codegen/src/test/resources/**.scala" +] + maxColumn = 120 preset = IntelliJ diff --git a/build.sbt b/build.sbt index a48b3fe..2ac47dc 100644 --- a/build.sbt +++ b/build.sbt @@ -40,3 +40,18 @@ lazy val `slick-additions-codegen` = "org.scalatest" %% "scalatest" % "3.2.17" % "test" ) ) + +lazy val `test-codegen` = + project + .in(`slick-additions-codegen`.base / "src" / "test" / "resources") + .dependsOn(`slick-additions`) + .settings( + publish / skip := true, + Compile / unmanagedSourceDirectories := Seq(baseDirectory.value), + scalacOptions += "-Ymacro-annotations", + libraryDependencies ++= Seq( + "com.typesafe.slick" %% "slick" % slickVersion, + "io.circe" %% "circe-generic" % "0.14.5", + "dev.optics" %% "monocle-macro" % "3.2.0" + ) + ) diff --git a/ci.sbt b/ci.sbt index db8a0f6..b444f61 100644 --- a/ci.sbt +++ b/ci.sbt @@ -1,4 +1,4 @@ -import _root_.io.github.nafg.mergify.dsl._ +import _root_.io.github.nafg.mergify.dsl.* mergifyExtraConditions := Seq( @@ -19,6 +19,22 @@ inThisBuild(List( dynverGitDescribeOutput ~= (_.map(o => o.copy(dirtySuffix = sbtdynver.GitDirtySuffix("")))), dynverSonatypeSnapshots := true, githubWorkflowTargetTags ++= Seq("v*"), + githubWorkflowBuildPostamble ++= + Seq( + WorkflowStep.Sbt( + commands = List( + "slick-additions-codegen/Test/runMain slick.additions.codegen.CodeGen", + "test-codegen/compile" + ), + name = Some("Check that codegen output compiles") + ), + WorkflowStep.Run( + commands = List( + "git diff --exit-code --quiet HEAD slick-additions-codegen/src/test/resources" + ), + name = Some("Check that codegen output hasn't changed") + ) + ), githubWorkflowPublishTargetBranches := Seq(RefPredicate.StartsWith(Ref.Tag("v"))), githubWorkflowPublish := Seq( WorkflowStep.Sbt( diff --git a/slick-additions-codegen/src/main/scala/slick/additions/codegen/EntityTableModulesCodeGenerator.scala b/slick-additions-codegen/src/main/scala/slick/additions/codegen/EntityTableModulesCodeGenerator.scala index 6ecfcc6..616d755 100644 --- a/slick-additions-codegen/src/main/scala/slick/additions/codegen/EntityTableModulesCodeGenerator.scala +++ b/slick-additions-codegen/src/main/scala/slick/additions/codegen/EntityTableModulesCodeGenerator.scala @@ -30,6 +30,8 @@ class EntityTableModulesCodeGenerator extends TablesCodeGenerator { """.stats } + override def mappingType(rowClassType: Type.Name) = t"slick.lifted.MappedProjection[$rowClassType]" + override def tableStats = { case tableConfig @ TableConfig(tableMetadata, tableClassName, modelClassName, columns) => columns.partition(c => tableMetadata.primaryKeys.exists(_.column == c.column.name)) match { diff --git a/slick-additions-codegen/src/main/scala/slick/additions/codegen/TablesCodeGenerator.scala b/slick-additions-codegen/src/main/scala/slick/additions/codegen/TablesCodeGenerator.scala index 5f587a1..54b2af0 100644 --- a/slick-additions-codegen/src/main/scala/slick/additions/codegen/TablesCodeGenerator.scala +++ b/slick-additions-codegen/src/main/scala/slick/additions/codegen/TablesCodeGenerator.scala @@ -17,6 +17,8 @@ class TablesCodeGenerator extends BaseCodeGenerator { // noinspection ScalaWeakerAccess def isDefaultSchema(schema: String) = schema == "public" + def mappingType(rowClassType: Type.Name) = t"slick.lifted.ProvenShape[$rowClassType]" + def mkMapping(rowClassName: String, mappingName: Term.Name, columns: List[ColumnConfig]) = { val companion = Term.Name(rowClassName) val rowClassType = Type.Name(rowClassName) @@ -53,7 +55,7 @@ class TablesCodeGenerator extends BaseCodeGenerator { (group22[Term](terms)(Term.Tuple(_)), fac, extractor) } - q"def $mappingName: MappedProjection[$rowClassType] = $tuple.<>({$factory}, $extractor)" + q"def $mappingName: ${mappingType(rowClassType)} = $tuple.<>({$factory}, $extractor)" } def columnField: ColumnConfig => Stat = { diff --git a/slick-additions-codegen/src/test/resources/config.conf b/slick-additions-codegen/src/test/resources/config.conf new file mode 100644 index 0000000..5221770 --- /dev/null +++ b/slick-additions-codegen/src/test/resources/config.conf @@ -0,0 +1,3 @@ +profile = "slick.jdbc.H2Profile$" +driver = "org.h2.Driver" +url = "jdbc:h2:mem:test2;DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM 'classpath:init.sql'" diff --git a/slick-additions-codegen/src/test/resources/Models.scala b/slick-additions-codegen/src/test/resources/entity/Models.scala similarity index 97% rename from slick-additions-codegen/src/test/resources/Models.scala rename to slick-additions-codegen/src/test/resources/entity/Models.scala index f61f856..8275333 100644 --- a/slick-additions-codegen/src/test/resources/Models.scala +++ b/slick-additions-codegen/src/test/resources/entity/Models.scala @@ -1,4 +1,4 @@ -package com.acme.models +package entity import slick.additions.entity.Lookup, io.circe.generic.JsonCodec, monocle.macros.Lenses @JsonCodec @Lenses case class ColorsRow(name: String) diff --git a/slick-additions-codegen/src/test/resources/TableModules.scala b/slick-additions-codegen/src/test/resources/entity/TableModules.scala similarity index 96% rename from slick-additions-codegen/src/test/resources/TableModules.scala rename to slick-additions-codegen/src/test/resources/entity/TableModules.scala index 767db8c..628046a 100644 --- a/slick-additions-codegen/src/test/resources/TableModules.scala +++ b/slick-additions-codegen/src/test/resources/entity/TableModules.scala @@ -1,4 +1,4 @@ -package com.acme.tablemodules +package entity import slick.additions.AdditionsProfile import slick.lifted.MappedProjection trait SlickProfile extends slick.jdbc.H2Profile with AdditionsProfile { @@ -13,7 +13,7 @@ object TableModules { class Row(tag: Tag) extends BaseEntRow(tag) { override def keyColumnName = "id" val name = column[String]("name") - def mapping: MappedProjection[ColorsRow] = name.<>( + def mapping: slick.lifted.MappedProjection[ColorsRow] = name.<>( { ColorsRow.apply }, @@ -47,7 +47,7 @@ object TableModules { val col22 = column[Option[Int]]("col22") val col23 = column[Option[Int]]("col23") val col24 = column[Option[Int]]("col24") - def mapping: MappedProjection[PeopleRow] = ( + def mapping: slick.lifted.MappedProjection[PeopleRow] = ( ( first, last, diff --git a/slick-additions-codegen/src/test/resources/entity/package.scala b/slick-additions-codegen/src/test/resources/entity/package.scala new file mode 100644 index 0000000..61fcfbe --- /dev/null +++ b/slick-additions-codegen/src/test/resources/entity/package.scala @@ -0,0 +1,10 @@ +import slick.additions.entity.{EntityKey, Lookup} + +import cats.implicits.toInvariantOps +import io.circe.{Codec, Decoder, Encoder} + + +package object entity { + implicit def codecLookup[K : Encoder: Decoder,A]: Codec[Lookup[K, A]] = + Codec.from(Decoder[K], Encoder[K]).imap[Lookup[K, A]](EntityKey[K, A])(_.key) +} diff --git a/slick-additions-codegen/src/test/resources/init.sql b/slick-additions-codegen/src/test/resources/init.sql new file mode 100644 index 0000000..202d912 --- /dev/null +++ b/slick-additions-codegen/src/test/resources/init.sql @@ -0,0 +1,30 @@ +CREATE TABLE IF NOT EXISTS "colors" ( + "id" BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + "name" VARCHAR NOT NULL +); +CREATE TABLE IF NOT EXISTS "people" ( + "id" BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + "first" VARCHAR NOT NULL, + "last" VARCHAR NOT NULL, + "city" VARCHAR NOT NULL DEFAULT 'New York', + "date_joined" DATE NOT NULL DEFAULT now(), + "balance" NUMERIC NOT NULL DEFAULT 0.0, + "best_friend" BIGINT NULL REFERENCES "people"("id") ON DELETE SET NULL, + "col8" FLOAT8 NULL, + "col9" BOOL NULL, + "col10" INT NULL, + "col11" INT NULL, + "col12" INT NULL, + "col13" INT NULL, + "col14" INT NULL, + "col15" INT NULL, + "col16" INT NULL, + "col17" INT NULL, + "col18" INT NULL, + "col19" INT NULL, + "col20" INT NULL, + "col21" INT NULL, + "col22" INT NULL, + "col23" INT NULL, + "col24" INT NULL +); diff --git a/slick-additions-codegen/src/test/resources/plain/Models.scala b/slick-additions-codegen/src/test/resources/plain/Models.scala new file mode 100644 index 0000000..0f35256 --- /dev/null +++ b/slick-additions-codegen/src/test/resources/plain/Models.scala @@ -0,0 +1,28 @@ +package plain +case class ColorsRow(id: Long, name: String) +case class PeopleRow( + id: Long, + first: String, + last: String, + city: String = "New York", + dateJoined: java.time.LocalDate = java.time.LocalDate.now(), + balance: BigDecimal = BigDecimal("0.0"), + bestFriend: Option[Long] = None, + col8: Option[Double] = None, + col9: Option[Boolean] = None, + col10: Option[Int] = None, + col11: Option[Int] = None, + col12: Option[Int] = None, + col13: Option[Int] = None, + col14: Option[Int] = None, + col15: Option[Int] = None, + col16: Option[Int] = None, + col17: Option[Int] = None, + col18: Option[Int] = None, + col19: Option[Int] = None, + col20: Option[Int] = None, + col21: Option[Int] = None, + col22: Option[Int] = None, + col23: Option[Int] = None, + col24: Option[Int] = None +) diff --git a/slick-additions-codegen/src/test/resources/Tables.scala b/slick-additions-codegen/src/test/resources/plain/Tables.scala similarity index 94% rename from slick-additions-codegen/src/test/resources/Tables.scala rename to slick-additions-codegen/src/test/resources/plain/Tables.scala index d0ed2d1..ed50e68 100644 --- a/slick-additions-codegen/src/test/resources/Tables.scala +++ b/slick-additions-codegen/src/test/resources/plain/Tables.scala @@ -1,12 +1,11 @@ -package com.acme.tables +package plain import slick.jdbc.H2Profile.api._ -import slick.additions.entity.Lookup object Tables { class Colors(_tableTag: Tag) extends Table[ColorsRow](_tableTag, Some("PUBLIC"), "colors") { val id = column[Long]("id") val name = column[String]("name") - def * : MappedProjection[ColorsRow] = (id, name).<>( + def * : slick.lifted.ProvenShape[ColorsRow] = (id, name).<>( { (ColorsRow.apply _).tupled }, @@ -22,7 +21,7 @@ object Tables { val city = column[String]("city") val dateJoined = column[java.time.LocalDate]("date_joined") val balance = column[BigDecimal]("balance") - val bestFriend = column[Option[Lookup[Long, PeopleRow]]]("best_friend") + val bestFriend = column[Option[Long]]("best_friend") val col8 = column[Option[Double]]("col8") val col9 = column[Option[Boolean]]("col9") val col10 = column[Option[Int]]("col10") @@ -40,7 +39,7 @@ object Tables { val col22 = column[Option[Int]]("col22") val col23 = column[Option[Int]]("col23") val col24 = column[Option[Int]]("col24") - def * : MappedProjection[PeopleRow] = ( + def * : slick.lifted.ProvenShape[PeopleRow] = ( ( id, first, diff --git a/slick-additions-codegen/src/test/scala/slick/additions/codegen/CodeGen.scala b/slick-additions-codegen/src/test/scala/slick/additions/codegen/CodeGen.scala new file mode 100644 index 0000000..830dbea --- /dev/null +++ b/slick-additions-codegen/src/test/scala/slick/additions/codegen/CodeGen.scala @@ -0,0 +1,9 @@ +package slick.additions.codegen + +import scala.concurrent.ExecutionContext.Implicits.global + + +object CodeGen extends App { + for (codeGeneration <- CodeGeneration.all) + Util.writeToFile(codeGeneration) +} diff --git a/slick-additions-codegen/src/test/scala/slick/additions/codegen/CodeGenTests.scala b/slick-additions-codegen/src/test/scala/slick/additions/codegen/CodeGenTests.scala new file mode 100644 index 0000000..2861a4f --- /dev/null +++ b/slick-additions-codegen/src/test/scala/slick/additions/codegen/CodeGenTests.scala @@ -0,0 +1,14 @@ +package slick.additions.codegen + +import scala.io.Source + +import org.scalatest.funsuite.AsyncFunSuite + + +class CodeGenTests extends AsyncFunSuite { + for (codeGeneration <- CodeGeneration.all) + test(codeGeneration.filename) { + Util.codeString(codeGeneration) + .map(assertResult(Source.fromResource(codeGeneration.filename).mkString)(_)) + } +} diff --git a/slick-additions-codegen/src/test/scala/slick/additions/codegen/CodeGeneration.scala b/slick-additions-codegen/src/test/scala/slick/additions/codegen/CodeGeneration.scala new file mode 100644 index 0000000..7b318dc --- /dev/null +++ b/slick-additions-codegen/src/test/scala/slick/additions/codegen/CodeGeneration.scala @@ -0,0 +1,26 @@ +package slick.additions.codegen + +import slick.additions.codegen.extra.circe.CirceJsonCodecModelsCodeGenerator +import slick.additions.codegen.extra.monocle.MonocleLensesModelsCodeGenerator + + +case class CodeGeneration(generator: BaseCodeGenerator, rules: GenerationRules) { + def pkgName = rules.packageName + val filename: String = s"${pkgName}/${rules.container}.scala" +} +object CodeGeneration { + class TestGenerationRules(override val container: String, override val packageName: String) + extends GenerationRules + val all = Seq( + CodeGeneration(new TablesCodeGenerator, new TestGenerationRules("Tables", "plain")), + CodeGeneration(new ModelsCodeGenerator, new TestGenerationRules("Models", "plain")), + CodeGeneration( + new EntityTableModulesCodeGenerator, + new TestGenerationRules("TableModules", "entity") with EntityGenerationRules + ), + CodeGeneration( + new KeylessModelsCodeGenerator with MonocleLensesModelsCodeGenerator with CirceJsonCodecModelsCodeGenerator, + new TestGenerationRules("Models", "entity") with EntityGenerationRules + ) + ) +} diff --git a/slick-additions-codegen/src/test/scala/slick/additions/codegen/CodegenTests.scala b/slick-additions-codegen/src/test/scala/slick/additions/codegen/CodegenTests.scala deleted file mode 100644 index 7bb17ca..0000000 --- a/slick-additions-codegen/src/test/scala/slick/additions/codegen/CodegenTests.scala +++ /dev/null @@ -1,111 +0,0 @@ -package slick.additions.codegen - -import java.nio.file.{Path, Paths} - -import scala.concurrent.Future -import scala.io.Source - -import slick.additions.codegen.extra.circe.CirceJsonCodecModelsCodeGenerator -import slick.additions.codegen.extra.monocle.MonocleLensesModelsCodeGenerator - -import com.typesafe.config.ConfigFactory -import org.scalatest.BeforeAndAfterAll -import org.scalatest.concurrent.ScalaFutures -import org.scalatest.funsuite.AsyncFunSuite -import org.scalatest.time.SpanSugar.convertIntToGrainOfTime - - -class CodegenTests extends AsyncFunSuite with BeforeAndAfterAll with ScalaFutures { - - import slick.jdbc.H2Profile.api._ - - - override implicit def patienceConfig: PatienceConfig = super.patienceConfig.copy(timeout = 10.seconds) - - val slickConfigHOCON = - """profile = "slick.jdbc.H2Profile$" - |driver = "org.h2.Driver" - |url = "jdbc:h2:mem:test2;DB_CLOSE_DELAY=-1" - |""".stripMargin - val slickConfig = ConfigFactory.parseString(slickConfigHOCON) - val db = Database.forConfig("", slickConfig) - - override protected def beforeAll() = - // noinspection SqlNoDataSourceInspection - db.run( - sqlu""" - CREATE TABLE "colors" ( - "id" BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, - "name" VARCHAR NOT NULL - ); - CREATE TABLE "people" ( - "id" BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, - "first" VARCHAR NOT NULL, - "last" VARCHAR NOT NULL, - "city" VARCHAR NOT NULL DEFAULT 'New York', - "date_joined" DATE NOT NULL DEFAULT now(), - "balance" NUMERIC NOT NULL DEFAULT 0.0, - "best_friend" BIGINT NULL REFERENCES "people" ("id") ON DELETE SET NULL, - "col8" FLOAT8 NULL, - "col9" BOOL NULL, - "col10" INT NULL, - "col11" INT NULL, - "col12" INT NULL, - "col13" INT NULL, - "col14" INT NULL, - "col15" INT NULL, - "col16" INT NULL, - "col17" INT NULL, - "col18" INT NULL, - "col19" INT NULL, - "col20" INT NULL, - "col21" INT NULL, - "col22" INT NULL, - "col23" INT NULL, - "col24" INT NULL - ); - """ - ).futureValue - override protected def afterAll() = db.close() - - def writeToFile(generator: BaseCodeGenerator, containerName: String): Future[Path] = - db.run( - generator.writeToFileDBIO( - Paths.get("target"), - slickConfig, - new EntityGenerationRules { - override def packageName: String = s"com.acme.${container.toLowerCase}" - override def container: String = containerName - } - ) - ) - - def codeString(generator: BaseCodeGenerator, containerName: String): Future[String] = - db.run( - generator.codeStringFormatted( - new EntityGenerationRules { - override def packageName: String = s"com.acme.${container.toLowerCase}" - override def container: String = containerName - }, - "slick.jdbc.H2Profile$" - ) - ) - - test("Tables") { - codeString(new TablesCodeGenerator, "Tables") - .map(assertResult(Source.fromResource("Tables.scala").mkString)(_)) - } - - test("EntityTableModules") { - codeString(new EntityTableModulesCodeGenerator, "TableModules") - .map(assertResult(Source.fromResource("TableModules.scala").mkString)(_)) - } - - test("Models") { - codeString( - new KeylessModelsCodeGenerator with MonocleLensesModelsCodeGenerator with CirceJsonCodecModelsCodeGenerator, - "Models" - ) - .map(assertResult(Source.fromResource("Models.scala").mkString)(_)) - } -} diff --git a/slick-additions-codegen/src/test/scala/slick/additions/codegen/Util.scala b/slick-additions-codegen/src/test/scala/slick/additions/codegen/Util.scala new file mode 100644 index 0000000..69cf7b0 --- /dev/null +++ b/slick-additions-codegen/src/test/scala/slick/additions/codegen/Util.scala @@ -0,0 +1,37 @@ +package slick.additions.codegen + +import java.nio.file.{Path, Paths} + +import scala.concurrent.{ExecutionContext, Future} + +import com.typesafe.config.ConfigFactory +import org.scalatest.CompleteLastly + + +object Util extends CompleteLastly { + private val profile = slick.jdbc.H2Profile + + import profile.api._ + + + private val slickConfig = ConfigFactory.parseResources("config.conf") + private def db = Database.forConfig("", slickConfig) + + def writeToFile(generation: CodeGeneration)(implicit executionContext: ExecutionContext) = + generation.generator.writeToFileSync( + Paths.get(s"slick-additions-codegen/src/test/resources/${generation.pkgName}"), + Util.slickConfig, + generation.rules + ) + + def codeString(generation: CodeGeneration)(implicit executionContext: ExecutionContext): Future[String] = + complete { + db.run( + generation.generator.codeStringFormatted( + generation.rules, + Util.slickConfig.getString("profile") + ) + ) + } + .lastly(db.close()) +}