This repository has been archived by the owner on May 15, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Backwards compatibility validator #3
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
af95788
Init empty validator project
sldblog 2cd90c5
Check compatibility of two register versions
sldblog 4ae36b4
Retrieve register files from previous commits
sldblog 0eb0d4a
Make runtime print out validation results
sldblog 8aa0b44
Add CI to unit test and validate compatibility
sldblog 2dfda36
Explicitly die if git commands fail
sldblog File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# | ||
# https://help.github.com/articles/dealing-with-line-endings/ | ||
# | ||
# These are explicitly windows files and should use crlf | ||
*.bat text eol=crlf | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
name: Build | ||
|
||
on: [push] | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v2 | ||
with: | ||
fetch-depth: 0 | ||
- uses: actions/setup-java@v2 | ||
with: | ||
distribution: 'adopt' | ||
java-version: '11' | ||
- run: ./gradlew check --console=plain | ||
|
||
compatibility: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v2 | ||
with: | ||
fetch-depth: 0 | ||
- uses: actions/setup-java@v2 | ||
with: | ||
distribution: 'adopt' | ||
java-version: '11' | ||
- run: ./gradlew assemble --console=plain | ||
- run: java -jar app/build/libs/app.jar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Ignore Gradle project-specific cache directory | ||
.gradle | ||
|
||
# Ignore Gradle build output directory | ||
build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
plugins { | ||
id("org.jetbrains.kotlin.jvm") version "1.4.31" | ||
application | ||
} | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
||
dependencies { | ||
implementation(platform("org.jetbrains.kotlin:kotlin-bom")) | ||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") | ||
implementation("com.opencsv:opencsv:5.4") | ||
testImplementation("org.jetbrains.kotlin:kotlin-test") | ||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit") | ||
} | ||
|
||
application { | ||
mainClass.set("uk.gov.justice.hmpps.referencedata.AppKt") | ||
} | ||
|
||
tasks.withType<Jar> { | ||
manifest.attributes["Main-Class"] = application.mainClass | ||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE | ||
|
||
from(sourceSets.main.get().output) | ||
dependsOn(configurations.runtimeClasspath) | ||
from({ configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) } }) | ||
} |
39 changes: 39 additions & 0 deletions
39
app/src/main/kotlin/uk/gov/justice/hmpps/referencedata/App.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package uk.gov.justice.hmpps.referencedata | ||
|
||
import java.io.FileReader | ||
import java.io.StringReader | ||
import kotlin.system.exitProcess | ||
|
||
data class Result(val register: Register, val compatibility: Compatibility) { | ||
fun hasErrors(): Boolean { | ||
return compatibility.errors.isNotEmpty() | ||
} | ||
} | ||
|
||
fun main(vararg args: String) { | ||
val ref = args.firstOrNull() ?: "origin/main" | ||
val registers = VersionedRegisters.fromPreviousCommit(ref) | ||
val results = registers.map { register -> | ||
val previousVersion = StringReader(register.content) | ||
val currentVersion = FileReader(register.path) | ||
Result(register, CheckCompatibility(previousVersion, currentVersion).check()) | ||
} | ||
|
||
println("🔍 Checking if register files are backwards compatible with '$ref'...") | ||
results.forEach(::printResult) | ||
|
||
if (results.any(Result::hasErrors)) { | ||
exitProcess(1) | ||
} | ||
} | ||
|
||
fun printResult(r: Result) { | ||
print("${r.register.path}:") | ||
val errors = r.compatibility.errors | ||
if (errors.isEmpty()) { | ||
println(" ✅ pass") | ||
} else { | ||
println() | ||
errors.forEach { println(" ❗ error: $it") } | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
app/src/main/kotlin/uk/gov/justice/hmpps/referencedata/CheckCompatibility.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package uk.gov.justice.hmpps.referencedata | ||
|
||
import com.opencsv.CSVReader | ||
import java.io.Reader | ||
|
||
data class Compatibility(val errors: List<String>) | ||
|
||
class CheckCompatibility(releasedVersion: Reader, currentVersion: Reader) { | ||
private var vReleased: MutableList<List<String>> | ||
private var vCurrent: MutableList<List<String>> | ||
private var requiredHeaders: List<String> | ||
private var newHeaders: List<String> | ||
|
||
init { | ||
vReleased = readAll(releasedVersion) | ||
vCurrent = readAll(currentVersion) | ||
requiredHeaders = vReleased.removeFirst() | ||
newHeaders = vCurrent.removeFirst() | ||
} | ||
|
||
fun check(): Compatibility { | ||
val errors = mutableListOf<String>() | ||
errors.addAll(verifyHeaders()) | ||
errors.addAll(verifyContent()) | ||
return Compatibility(errors) | ||
} | ||
|
||
private fun verifyHeaders(): List<String> { | ||
val errors = arrayListOf<String>() | ||
|
||
val removedHeaders = requiredHeaders.subtract(newHeaders) | ||
for (h in removedHeaders) { | ||
errors.add("previously used column cannot be removed: $h") | ||
} | ||
|
||
return errors | ||
} | ||
|
||
private fun verifyContent(): List<String> { | ||
val errors = arrayListOf<String>() | ||
|
||
val releasedIds = vReleased.map { row -> row.first() } | ||
val currentIds = vCurrent.map { row -> row.first() } | ||
|
||
val removedIds = releasedIds.subtract(currentIds) | ||
for (removedId in removedIds) { | ||
errors.add("previously used row cannot be removed: row with ID `$removedId` is missing") | ||
} | ||
|
||
return errors | ||
} | ||
|
||
private fun readAll(r: Reader): MutableList<List<String>> { | ||
val csvr = CSVReader(r) | ||
val rows = mutableListOf<List<String>>() | ||
|
||
var l = csvr.readNext() | ||
while (l != null) { | ||
rows.add(l.toList()) | ||
l = csvr.readNext() | ||
} | ||
|
||
return rows | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
app/src/main/kotlin/uk/gov/justice/hmpps/referencedata/VersionedRegisters.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package uk.gov.justice.hmpps.referencedata | ||
|
||
import java.io.File | ||
import java.lang.RuntimeException | ||
import java.util.concurrent.TimeUnit | ||
|
||
data class Register(val path: String, val content: String) | ||
|
||
class VersionedRegisters { | ||
companion object { | ||
fun fromPreviousCommit(shaOrRef: String): List<Register> { | ||
val c = ProcessBuilder("git", "ls-tree", "-r", "--full-tree", "--name-only", shaOrRef) | ||
val p = c.start() | ||
p.waitFor(10, TimeUnit.SECONDS) | ||
if (p.exitValue() != 0) { | ||
throw RuntimeException("Command ${c.command()} failed with exit status ${p.exitValue()}") | ||
} | ||
|
||
val registries = mutableListOf<Register>() | ||
for (path in p.inputStream.bufferedReader().lines()) { | ||
if (!path.endsWith(".csv", true)) { | ||
continue | ||
} | ||
if (path.contains("test/resources/")) { | ||
continue | ||
} | ||
registries.add(Register(path, checkoutFile(shaOrRef, path))) | ||
} | ||
|
||
return registries | ||
} | ||
|
||
private fun checkoutFile(shaOrRef: String, path: String): String { | ||
val tempfile = File.createTempFile("reference-data", "") | ||
tempfile.deleteOnExit() | ||
|
||
val p = ProcessBuilder("git", "show", "$shaOrRef:$path") | ||
.redirectOutput(tempfile) | ||
.start() | ||
p.waitFor(10, TimeUnit.SECONDS) | ||
|
||
return tempfile.readText() | ||
} | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
app/src/test/kotlin/uk/gov/justice/hmpps/referencedata/CheckCompatibilityTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package uk.gov.justice.hmpps.referencedata | ||
|
||
import java.io.FileReader | ||
import java.io.Reader | ||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
import kotlin.test.assertTrue | ||
|
||
class CheckCompatibilityTest { | ||
@Test | ||
fun addingNewColumnIsAllowed() { | ||
val compatibility = checkCompatibility("/new_column_v1.csv", "/new_column_v2.csv") | ||
assertTrue(compatibility.errors.isEmpty()) | ||
} | ||
|
||
@Test | ||
fun addingNewRowIsAllowed() { | ||
val compatibility = checkCompatibility("/new_row_v1.csv", "/new_row_v2.csv") | ||
assertTrue(compatibility.errors.isEmpty()) | ||
} | ||
|
||
@Test | ||
fun changingAContentFieldIsAllowed() { | ||
val compatibility = checkCompatibility("/change_content_v1.csv", "/change_content_v2.csv") | ||
assertTrue(compatibility.errors.isEmpty()) | ||
} | ||
|
||
@Test | ||
fun removingAColumnIsDisallowed() { | ||
val compatibility = checkCompatibility("/rename_column_v1.csv", "/rename_column_v2.csv") | ||
assertEquals( | ||
listOf("previously used column cannot be removed: valid_untll"), | ||
compatibility.errors | ||
) | ||
} | ||
|
||
@Test | ||
fun removingARowIsDisallowed() { | ||
val compatibility = checkCompatibility("/remove_row_v1.csv", "/remove_row_v2.csv") | ||
assertEquals( | ||
listOf("previously used row cannot be removed: row with ID `A` is missing"), | ||
compatibility.errors | ||
) | ||
} | ||
|
||
@Test | ||
fun changingAPrimaryKeyIsDisallowed() { | ||
val compatibility = checkCompatibility("/change_primary_key_v1.csv", "/change_primary_key_v2.csv") | ||
assertEquals( | ||
listOf("previously used row cannot be removed: row with ID `A` is missing"), | ||
compatibility.errors | ||
) | ||
} | ||
|
||
private fun checkCompatibility(before: String, after: String): Compatibility = | ||
CheckCompatibility(fixture(before), fixture(after)).check() | ||
|
||
private fun fixture(filename: String): Reader = FileReader(javaClass.getResource(filename).file) | ||
} |
42 changes: 42 additions & 0 deletions
42
app/src/test/kotlin/uk/gov/justice/hmpps/referencedata/VersionedRegistersTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package uk.gov.justice.hmpps.referencedata | ||
|
||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
import kotlin.test.assertFalse | ||
import kotlin.test.assertTrue | ||
|
||
class VersionedRegistersTest { | ||
private val firstRepoCommitWithRegister: String = "b8478787640aebddc15d9b80dd3ea6cde6541b44" | ||
private val commitWithTestFixtures: String = "2cd90c5ddcf897ab8ac3173ea2c4fd1aa490888b" | ||
|
||
@Test | ||
fun retrievesPreviousVersionsOfRegisters() { | ||
val registers = VersionedRegisters.fromPreviousCommit(firstRepoCommitWithRegister) | ||
assertTrue(registers.isNotEmpty()) | ||
|
||
val filenames = registers.map(Register::path) | ||
assertEquals(listOf("probation-regions.csv"), filenames) | ||
|
||
val content = registers.first().content.trim().split("\n") | ||
assertEquals("id,name", content.first()) | ||
assertEquals("L,\"Greater Manchester\"", content.last()) | ||
} | ||
|
||
@Test | ||
fun doesNotRetrieveNonRegisterFiles() { | ||
val registers = VersionedRegisters.fromPreviousCommit(firstRepoCommitWithRegister) | ||
assertTrue(registers.isNotEmpty()) | ||
|
||
val filenames = registers.map(Register::path) | ||
assertFalse(filenames.contains("README.md")) | ||
} | ||
|
||
@Test | ||
fun doesNotRetrieveTestRegisters() { | ||
val registers = VersionedRegisters.fromPreviousCommit(commitWithTestFixtures) | ||
assertTrue(registers.isNotEmpty()) | ||
|
||
val filenames = registers.map(Register::path) | ||
assertFalse(filenames.any { path -> path.contains("new_column_v1.csv") }) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
id,name | ||
A,"North East" | ||
B,"North Wrst" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
id,name | ||
A,"North East" | ||
B,"North West" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
id,name | ||
A,"North East" | ||
B,"North West" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
id,name | ||
X,"North East" | ||
B,"North West" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
id,name | ||
A,"North East" | ||
B,"North West" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
id,name,valid_until | ||
A,"North East", | ||
B,"North West",2021-05-01 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
id,name | ||
A,"North East" | ||
B,"North West" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
id,name | ||
A,"North East" | ||
B,"North West" | ||
C,"Yorkshire and the Humber" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
id,name | ||
A,"North East" | ||
B,"North West" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because the app logic relies on git history;
fetch-depth: 0
means fetch the entire history, not just a single commit