From 1bac893d8d922c53ec164ea45ef7211cad67d1f2 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 18 May 2022 19:07:08 +1000 Subject: [PATCH 1/4] content hash class to parse json file, di and args --- cli/BUILD | 12 +++++ .../bazel_diff/cli/GenerateHashesCommand.kt | 9 ++++ .../main/kotlin/com/bazel_diff/di/Modules.kt | 3 ++ .../com/bazel_diff/hash/SourceFileHasher.kt | 2 + .../kotlin/com/bazel_diff/io/ContentHash.kt | 25 +++++++++ .../com/bazel_diff/io/ContentHashTest.kt | 51 +++++++++++++++++++ .../com/bazel_diff/io/fixture/correct.json | 4 ++ .../com/bazel_diff/io/fixture/wrong.json | 5 ++ 8 files changed, 111 insertions(+) create mode 100644 cli/src/main/kotlin/com/bazel_diff/io/ContentHash.kt create mode 100644 cli/src/test/kotlin/com/bazel_diff/io/ContentHashTest.kt create mode 100644 cli/src/test/kotlin/com/bazel_diff/io/fixture/correct.json create mode 100644 cli/src/test/kotlin/com/bazel_diff/io/fixture/wrong.json diff --git a/cli/BUILD b/cli/BUILD index 22077a6..c6a0658 100644 --- a/cli/BUILD +++ b/cli/BUILD @@ -89,6 +89,18 @@ kt_jvm_test( runtime_deps = [":cli-test-lib"], ) +kt_jvm_test( + name = "ContentHashTest", + data = [ + ":src/test/kotlin/com/bazel_diff/io/fixture/correct.json", + ":src/test/kotlin/com/bazel_diff/io/fixture/wrong.json", + ], + test_class = "com.bazel_diff.io.ContentHashTest", + runtime_deps = [ + ":cli-test-lib", + ], +) + kt_jvm_library( name = "cli-test-lib", testonly = True, diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt b/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt index 259c072..1df6a0e 100644 --- a/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt +++ b/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt @@ -40,6 +40,14 @@ class GenerateHashesCommand : Callable { ) lateinit var bazelPath: Path + @CommandLine.Option( + names = ["--contentHashPath"], + description = ["Path to content hash json file. It's a map which maps relative file path from workspace path to its content hash. Files in this map will skip content hashing"], + scope = CommandLine.ScopeType.INHERIT, + required = false + ) + var contentHashPath: Path? = null + @CommandLine.Option( names = ["-so", "--bazelStartupOptions"], description = ["Additional space separated Bazel client startup options used when invoking Bazel"], @@ -87,6 +95,7 @@ class GenerateHashesCommand : Callable { hasherModule( workspacePath, bazelPath, + contentHashPath, bazelStartupOptions, bazelCommandOptions, keepGoing, diff --git a/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt b/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt index ec04ca4..ec95e38 100644 --- a/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt +++ b/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt @@ -6,6 +6,7 @@ import com.bazel_diff.hash.BuildGraphHasher import com.bazel_diff.hash.RuleHasher import com.bazel_diff.hash.SourceFileHasher import com.bazel_diff.hash.TargetHasher +import com.bazel_diff.io.ContentHash import com.bazel_diff.log.Logger import com.bazel_diff.log.StdoutLogger import com.google.gson.GsonBuilder @@ -17,6 +18,7 @@ import java.nio.file.Path fun hasherModule( workingDirectory: Path, bazelPath: Path, + contentHashPath: Path?, startupOptions: List, commandOptions: List, keepGoing: Boolean?, @@ -38,6 +40,7 @@ fun hasherModule( single { RuleHasher() } single { SourceFileHasher() } single(named("working-directory")) { workingDirectory } + single { ContentHash(contentHashPath) } } fun loggingModule(verbose: Boolean) = module { diff --git a/cli/src/main/kotlin/com/bazel_diff/hash/SourceFileHasher.kt b/cli/src/main/kotlin/com/bazel_diff/hash/SourceFileHasher.kt index 30227fb..2baf498 100644 --- a/cli/src/main/kotlin/com/bazel_diff/hash/SourceFileHasher.kt +++ b/cli/src/main/kotlin/com/bazel_diff/hash/SourceFileHasher.kt @@ -1,6 +1,7 @@ package com.bazel_diff.hash import com.bazel_diff.bazel.BazelSourceFileTarget +import com.bazel_diff.io.ContentHash import com.bazel_diff.log.Logger import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -11,6 +12,7 @@ import java.nio.file.Paths class SourceFileHasher : KoinComponent { private val workingDirectory: Path by inject(qualifier = named("working-directory")) private val logger: Logger by inject() + private val contentHash: ContentHash by inject() fun digest(sourceFileTarget: BazelSourceFileTarget): ByteArray { return sha256 { diff --git a/cli/src/main/kotlin/com/bazel_diff/io/ContentHash.kt b/cli/src/main/kotlin/com/bazel_diff/io/ContentHash.kt new file mode 100644 index 0000000..1b02574 --- /dev/null +++ b/cli/src/main/kotlin/com/bazel_diff/io/ContentHash.kt @@ -0,0 +1,25 @@ +package com.bazel_diff.io + +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import java.nio.file.Files +import java.nio.file.Path + + +class ContentHash(path: Path?) { + // filename relative to workspace -> content hash of the file + var filenameToHash: Map? = null + + init { + path?.let { + this.filenameToHash = readJson(it) + } + } + + private fun readJson(file: Path): Map { + val gson = Gson() + val reader = Files.newBufferedReader(file) + val shape = object : TypeToken>() {}.type + return gson.fromJson(reader, shape) + } +} diff --git a/cli/src/test/kotlin/com/bazel_diff/io/ContentHashTest.kt b/cli/src/test/kotlin/com/bazel_diff/io/ContentHashTest.kt new file mode 100644 index 0000000..c994901 --- /dev/null +++ b/cli/src/test/kotlin/com/bazel_diff/io/ContentHashTest.kt @@ -0,0 +1,51 @@ +package com.bazel_diff.io + +import assertk.assertThat +import assertk.assertions.* +import com.bazel_diff.testModule +import com.google.gson.JsonSyntaxException +import kotlinx.coroutines.runBlocking +import org.junit.Rule +import org.junit.Test +import org.koin.test.KoinTest +import org.koin.test.KoinTestRule +import kotlin.io.path.Path + + +internal class ContentHashTest: KoinTest { + @get:Rule + val koinTestRule = KoinTestRule.create { + modules(testModule()) + } + + @Test + fun testNullPath() = runBlocking { + val contentHash = ContentHash(null) + assertThat(contentHash.filenameToHash).isNull() + } + + @Test + fun testNonExistingPath() = runBlocking { + assertThat { + ContentHash(Path("/not/exists")) + }.isFailure().hasClass(java.nio.file.NoSuchFileException::class) + } + + @Test + fun testParseJsonFileWithWrongShape() = runBlocking { + val path = Path("cli/src/test/kotlin/com/bazel_diff/io/fixture/wrong.json") + assertThat { + ContentHash(path) + }.isFailure().hasClass(JsonSyntaxException::class) + } + + @Test + fun testParseJsonFileWithCorrectShape() = runBlocking { + val path = Path("cli/src/test/kotlin/com/bazel_diff/io/fixture/correct.json") + val map = ContentHash(path).filenameToHash + assertThat(map).isNotNull().containsOnly( + "foo" to "content-hash-for-foo", + "bar" to "content-hash-for-bar" + ) + } +} diff --git a/cli/src/test/kotlin/com/bazel_diff/io/fixture/correct.json b/cli/src/test/kotlin/com/bazel_diff/io/fixture/correct.json new file mode 100644 index 0000000..100cc6c --- /dev/null +++ b/cli/src/test/kotlin/com/bazel_diff/io/fixture/correct.json @@ -0,0 +1,4 @@ +{ + "foo": "content-hash-for-foo", + "bar": "content-hash-for-bar" +} diff --git a/cli/src/test/kotlin/com/bazel_diff/io/fixture/wrong.json b/cli/src/test/kotlin/com/bazel_diff/io/fixture/wrong.json new file mode 100644 index 0000000..ebbc37d --- /dev/null +++ b/cli/src/test/kotlin/com/bazel_diff/io/fixture/wrong.json @@ -0,0 +1,5 @@ +{ + "foo": { + "bar": "abcd" + } +} From 5125715dbd23b4b4761323bffa38ec9df6d5ac8c Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 18 May 2022 21:04:56 +1000 Subject: [PATCH 2/4] use provided content hash whenever possible --- cli/BUILD | 13 +- .../main/kotlin/com/bazel_diff/di/Modules.kt | 4 +- .../com/bazel_diff/hash/SourceFileHasher.kt | 44 ++++-- ...{ContentHash.kt => ContentHashProvider.kt} | 11 +- cli/src/test/kotlin/com/bazel_diff/Modules.kt | 7 +- .../bazel_diff/hash/SourceFileHasherTest.kt | 143 ++++++++++++++++++ .../kotlin/com/bazel_diff/hash/fixture/foo.ts | 1 + ...HashTest.kt => ContentHashProviderTest.kt} | 13 +- 8 files changed, 203 insertions(+), 33 deletions(-) rename cli/src/main/kotlin/com/bazel_diff/io/{ContentHash.kt => ContentHashProvider.kt} (72%) create mode 100644 cli/src/test/kotlin/com/bazel_diff/hash/SourceFileHasherTest.kt create mode 100644 cli/src/test/kotlin/com/bazel_diff/hash/fixture/foo.ts rename cli/src/test/kotlin/com/bazel_diff/io/{ContentHashTest.kt => ContentHashProviderTest.kt} (78%) diff --git a/cli/BUILD b/cli/BUILD index c6a0658..b082b05 100644 --- a/cli/BUILD +++ b/cli/BUILD @@ -59,6 +59,15 @@ kt_jvm_test( runtime_deps = [":cli-test-lib"], ) +kt_jvm_test( + name = "SourceFileHasherTest", + data = [ + ":src/test/kotlin/com/bazel_diff/hash/fixture/foo.ts", + ], + test_class = "com.bazel_diff.hash.SourceFileHasherTest", + runtime_deps = [":cli-test-lib"], +) + kt_jvm_test( name = "CalculateImpactedTargetsInteractorTest", test_class = "com.bazel_diff.interactor.CalculateImpactedTargetsInteractorTest", @@ -90,12 +99,12 @@ kt_jvm_test( ) kt_jvm_test( - name = "ContentHashTest", + name = "ContentHashProviderTest", data = [ ":src/test/kotlin/com/bazel_diff/io/fixture/correct.json", ":src/test/kotlin/com/bazel_diff/io/fixture/wrong.json", ], - test_class = "com.bazel_diff.io.ContentHashTest", + test_class = "com.bazel_diff.io.ContentHashProviderTest", runtime_deps = [ ":cli-test-lib", ], diff --git a/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt b/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt index ec95e38..dbc3a33 100644 --- a/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt +++ b/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt @@ -6,7 +6,7 @@ import com.bazel_diff.hash.BuildGraphHasher import com.bazel_diff.hash.RuleHasher import com.bazel_diff.hash.SourceFileHasher import com.bazel_diff.hash.TargetHasher -import com.bazel_diff.io.ContentHash +import com.bazel_diff.io.ContentHashProvider import com.bazel_diff.log.Logger import com.bazel_diff.log.StdoutLogger import com.google.gson.GsonBuilder @@ -40,7 +40,7 @@ fun hasherModule( single { RuleHasher() } single { SourceFileHasher() } single(named("working-directory")) { workingDirectory } - single { ContentHash(contentHashPath) } + single { ContentHashProvider(contentHashPath) } } fun loggingModule(verbose: Boolean) = module { diff --git a/cli/src/main/kotlin/com/bazel_diff/hash/SourceFileHasher.kt b/cli/src/main/kotlin/com/bazel_diff/hash/SourceFileHasher.kt index 2baf498..ae041cd 100644 --- a/cli/src/main/kotlin/com/bazel_diff/hash/SourceFileHasher.kt +++ b/cli/src/main/kotlin/com/bazel_diff/hash/SourceFileHasher.kt @@ -1,7 +1,7 @@ package com.bazel_diff.hash import com.bazel_diff.bazel.BazelSourceFileTarget -import com.bazel_diff.io.ContentHash +import com.bazel_diff.io.ContentHashProvider import com.bazel_diff.log.Logger import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -10,22 +10,46 @@ import java.nio.file.Path import java.nio.file.Paths class SourceFileHasher : KoinComponent { - private val workingDirectory: Path by inject(qualifier = named("working-directory")) - private val logger: Logger by inject() - private val contentHash: ContentHash by inject() + private val workingDirectory: Path + private val logger: Logger + private val relativeFilenameToContentHash: Map? + init { + val logger: Logger by inject() + this.logger = logger + } + + constructor() { + val workingDirectory: Path by inject(qualifier = named("working-directory")) + this.workingDirectory = workingDirectory + val contentHashProvider: ContentHashProvider by inject() + relativeFilenameToContentHash = contentHashProvider.filenameToHash + } + + constructor(workingDirectory: Path, relativeFilenameToContentHash: Map?) { + this.workingDirectory = workingDirectory + this.relativeFilenameToContentHash = relativeFilenameToContentHash + } fun digest(sourceFileTarget: BazelSourceFileTarget): ByteArray { return sha256 { val name = sourceFileTarget.name if (name.startsWith("//")) { val filenameSubstring = name.substring(2) - val filenamePath = filenameSubstring.replaceFirst(":".toRegex(), "/") - val absoluteFilePath = Paths.get(workingDirectory.toString(), filenamePath) - val file = absoluteFilePath.toFile() - if (file.exists() && file.isFile) { - putFile(file) + val filenamePath = filenameSubstring.replaceFirst( + ":".toRegex(), + if (filenameSubstring.startsWith(":")) "" else "/" + ) + if (relativeFilenameToContentHash?.contains(filenamePath) == true) { + val contentHash = relativeFilenameToContentHash.getValue(filenamePath) + safePutBytes(contentHash.toByteArray()) } else { - logger.w { "File $absoluteFilePath not found" } + val absoluteFilePath = Paths.get(workingDirectory.toString(), filenamePath) + val file = absoluteFilePath.toFile() + if (file.exists() && file.isFile) { + putFile(file) + } else { + logger.w { "File $absoluteFilePath not found" } + } } safePutBytes(sourceFileTarget.seed) safePutBytes(name.toByteArray()) diff --git a/cli/src/main/kotlin/com/bazel_diff/io/ContentHash.kt b/cli/src/main/kotlin/com/bazel_diff/io/ContentHashProvider.kt similarity index 72% rename from cli/src/main/kotlin/com/bazel_diff/io/ContentHash.kt rename to cli/src/main/kotlin/com/bazel_diff/io/ContentHashProvider.kt index 1b02574..d877e72 100644 --- a/cli/src/main/kotlin/com/bazel_diff/io/ContentHash.kt +++ b/cli/src/main/kotlin/com/bazel_diff/io/ContentHashProvider.kt @@ -5,16 +5,9 @@ import com.google.gson.reflect.TypeToken import java.nio.file.Files import java.nio.file.Path - -class ContentHash(path: Path?) { +class ContentHashProvider(path: Path?) { // filename relative to workspace -> content hash of the file - var filenameToHash: Map? = null - - init { - path?.let { - this.filenameToHash = readJson(it) - } - } + val filenameToHash: Map? = if (path == null) null else readJson(path) private fun readJson(file: Path): Map { val gson = Gson() diff --git a/cli/src/test/kotlin/com/bazel_diff/Modules.kt b/cli/src/test/kotlin/com/bazel_diff/Modules.kt index b03974a..db51971 100644 --- a/cli/src/test/kotlin/com/bazel_diff/Modules.kt +++ b/cli/src/test/kotlin/com/bazel_diff/Modules.kt @@ -1,18 +1,17 @@ package com.bazel_diff import com.bazel_diff.bazel.BazelClient -import com.bazel_diff.bazel.BazelQueryService import com.bazel_diff.hash.BuildGraphHasher import com.bazel_diff.hash.RuleHasher import com.bazel_diff.hash.SourceFileHasher import com.bazel_diff.hash.TargetHasher +import com.bazel_diff.io.ContentHashProvider import com.bazel_diff.log.Logger -import com.bazel_diff.log.StdoutLogger import com.google.gson.GsonBuilder import org.koin.core.module.Module import org.koin.core.qualifier.named import org.koin.dsl.module -import java.nio.file.Path +import java.nio.file.Paths fun testModule(): Module = module { single { SilentLogger } @@ -22,6 +21,8 @@ fun testModule(): Module = module { single { RuleHasher() } single { SourceFileHasher() } single { GsonBuilder().setPrettyPrinting().create() } + single(named("working-directory")) { Paths.get("working-directory") } + single { ContentHashProvider(null) } } object SilentLogger : Logger { diff --git a/cli/src/test/kotlin/com/bazel_diff/hash/SourceFileHasherTest.kt b/cli/src/test/kotlin/com/bazel_diff/hash/SourceFileHasherTest.kt new file mode 100644 index 0000000..47c537b --- /dev/null +++ b/cli/src/test/kotlin/com/bazel_diff/hash/SourceFileHasherTest.kt @@ -0,0 +1,143 @@ +package com.bazel_diff.hash + +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.isNull +import com.bazel_diff.bazel.BazelSourceFileTarget +import com.bazel_diff.extensions.toHexString +import com.bazel_diff.testModule +import kotlinx.coroutines.runBlocking +import org.junit.Rule +import org.junit.Test +import org.koin.test.KoinTest +import org.koin.test.KoinTestRule +import java.nio.file.Files +import java.nio.file.Paths + + +internal class SourceFileHasherTest: KoinTest { + private val repoAbsolutePath = Paths.get("").toAbsolutePath() + private val fixtureFileTarget = "//cli/src/test/kotlin/com/bazel_diff/hash/fixture:foo.ts" + private val fixtureFileContent: ByteArray + private val seed = "seed".toByteArray() + + init { + val path = Paths.get("cli/src/test/kotlin/com/bazel_diff/hash/fixture/foo.ts") + fixtureFileContent = Files.readAllBytes(path) + } + + + @get:Rule + val koinTestRule = KoinTestRule.create { + modules(testModule()) + } + + @Test + fun testHashConcreteFile() = runBlocking { + val hasher = SourceFileHasher(repoAbsolutePath, null) + val bazelSourceFileTarget = BazelSourceFileTarget(fixtureFileTarget, seed) + val actual = hasher.digest(bazelSourceFileTarget).toHexString() + val expected = sha256 { + safePutBytes(fixtureFileContent) + safePutBytes(seed) + safePutBytes(fixtureFileTarget.toByteArray()) + }.toHexString() + assertThat(actual).isEqualTo(expected) + } + + @Test + fun testSoftHashConcreteFile() = runBlocking { + val hasher = SourceFileHasher(repoAbsolutePath, null) + val bazelSourceFileTarget = BazelSourceFileTarget(fixtureFileTarget, seed) + val actual = hasher.softDigest(bazelSourceFileTarget)?.toHexString() + val expected = sha256 { + safePutBytes(fixtureFileContent) + safePutBytes(seed) + safePutBytes(fixtureFileTarget.toByteArray()) + }.toHexString() + assertThat(actual).isEqualTo(expected) + } + + @Test + fun testSoftHashNonExistedFile() = runBlocking { + val hasher = SourceFileHasher(repoAbsolutePath, null) + val bazelSourceFileTarget = BazelSourceFileTarget("//i/do/not/exist", seed) + val actual = hasher.softDigest(bazelSourceFileTarget) + assertThat(actual).isNull() + } + + @Test + fun testSoftHashExternalTarget() = runBlocking { + val target = "@bazel-diff//some:file" + val hasher = SourceFileHasher(repoAbsolutePath, null) + val bazelSourceFileTarget = BazelSourceFileTarget(target, seed) + val actual = hasher.softDigest(bazelSourceFileTarget) + assertThat(actual).isNull() + } + + @Test + fun testHashNonExistedFile() = runBlocking { + val target = "//i/do/not/exist" + val hasher = SourceFileHasher(repoAbsolutePath, null) + val bazelSourceFileTarget = BazelSourceFileTarget(target, seed) + val actual = hasher.digest(bazelSourceFileTarget).toHexString() + val expected = sha256 { + safePutBytes(seed) + safePutBytes(target.toByteArray()) + }.toHexString() + assertThat(actual).isEqualTo(expected) + } + + @Test + fun testHashExternalTarget() = runBlocking { + val target = "@bazel-diff//some:file" + val hasher = SourceFileHasher(repoAbsolutePath, null) + val bazelSourceFileTarget = BazelSourceFileTarget(target, seed) + val actual = hasher.digest(bazelSourceFileTarget).toHexString() + val expected = sha256 {}.toHexString() + assertThat(actual).isEqualTo(expected) + } + + @Test + fun testHashWithProvidedContentHash() = runBlocking { + val filenameToContentHash = hashMapOf("cli/src/test/kotlin/com/bazel_diff/hash/fixture/foo.ts" to "foo-content-hash") + val hasher = SourceFileHasher(repoAbsolutePath, filenameToContentHash) + val bazelSourceFileTarget = BazelSourceFileTarget(fixtureFileTarget, seed) + val actual = hasher.digest(bazelSourceFileTarget).toHexString() + val expected = sha256 { + safePutBytes("foo-content-hash".toByteArray()) + safePutBytes(seed) + safePutBytes(fixtureFileTarget.toByteArray()) + }.toHexString() + assertThat(actual).isEqualTo(expected) + } + + @Test + fun testHashWithProvidedContentHashButNotInKey() = runBlocking { + val filenameToContentHash = hashMapOf("cli/src/test/kotlin/com/bazel_diff/hash/fixture/bar.ts" to "foo-content-hash") + val hasher = SourceFileHasher(repoAbsolutePath, filenameToContentHash) + val bazelSourceFileTarget = BazelSourceFileTarget(fixtureFileTarget, seed) + val actual = hasher.digest(bazelSourceFileTarget).toHexString() + val expected = sha256 { + safePutBytes(fixtureFileContent) + safePutBytes(seed) + safePutBytes(fixtureFileTarget.toByteArray()) + }.toHexString() + assertThat(actual).isEqualTo(expected) + } + + @Test + fun testHashWithProvidedContentHashWithLeadingColon() = runBlocking { + val targetName = "//:cli/src/test/kotlin/com/bazel_diff/hash/fixture/bar.ts" + val filenameToContentHash = hashMapOf("cli/src/test/kotlin/com/bazel_diff/hash/fixture/bar.ts" to "foo-content-hash") + val hasher = SourceFileHasher(repoAbsolutePath, filenameToContentHash) + val bazelSourceFileTarget = BazelSourceFileTarget(targetName, seed) + val actual = hasher.digest(bazelSourceFileTarget).toHexString() + val expected = sha256 { + safePutBytes("foo-content-hash".toByteArray()) + safePutBytes(seed) + safePutBytes(targetName.toByteArray()) + }.toHexString() + assertThat(actual).isEqualTo(expected) + } +} diff --git a/cli/src/test/kotlin/com/bazel_diff/hash/fixture/foo.ts b/cli/src/test/kotlin/com/bazel_diff/hash/fixture/foo.ts new file mode 100644 index 0000000..9425850 --- /dev/null +++ b/cli/src/test/kotlin/com/bazel_diff/hash/fixture/foo.ts @@ -0,0 +1 @@ +console.log('123') diff --git a/cli/src/test/kotlin/com/bazel_diff/io/ContentHashTest.kt b/cli/src/test/kotlin/com/bazel_diff/io/ContentHashProviderTest.kt similarity index 78% rename from cli/src/test/kotlin/com/bazel_diff/io/ContentHashTest.kt rename to cli/src/test/kotlin/com/bazel_diff/io/ContentHashProviderTest.kt index c994901..de02f38 100644 --- a/cli/src/test/kotlin/com/bazel_diff/io/ContentHashTest.kt +++ b/cli/src/test/kotlin/com/bazel_diff/io/ContentHashProviderTest.kt @@ -11,8 +11,7 @@ import org.koin.test.KoinTest import org.koin.test.KoinTestRule import kotlin.io.path.Path - -internal class ContentHashTest: KoinTest { +internal class ContentHashProviderTest: KoinTest { @get:Rule val koinTestRule = KoinTestRule.create { modules(testModule()) @@ -20,14 +19,14 @@ internal class ContentHashTest: KoinTest { @Test fun testNullPath() = runBlocking { - val contentHash = ContentHash(null) - assertThat(contentHash.filenameToHash).isNull() + val contentHashProvider = ContentHashProvider(null) + assertThat(contentHashProvider.filenameToHash).isNull() } @Test fun testNonExistingPath() = runBlocking { assertThat { - ContentHash(Path("/not/exists")) + ContentHashProvider(Path("/not/exists")) }.isFailure().hasClass(java.nio.file.NoSuchFileException::class) } @@ -35,14 +34,14 @@ internal class ContentHashTest: KoinTest { fun testParseJsonFileWithWrongShape() = runBlocking { val path = Path("cli/src/test/kotlin/com/bazel_diff/io/fixture/wrong.json") assertThat { - ContentHash(path) + ContentHashProvider(path) }.isFailure().hasClass(JsonSyntaxException::class) } @Test fun testParseJsonFileWithCorrectShape() = runBlocking { val path = Path("cli/src/test/kotlin/com/bazel_diff/io/fixture/correct.json") - val map = ContentHash(path).filenameToHash + val map = ContentHashProvider(path).filenameToHash assertThat(map).isNotNull().containsOnly( "foo" to "content-hash-for-foo", "bar" to "content-hash-for-bar" From 15c919ebd87dac49bae1c507123fabb31d4b8641 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 18 May 2022 21:48:38 +1000 Subject: [PATCH 3/4] update docs --- README.md | 5 +++++ .../kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 31db546..93e52a2 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,11 @@ workspace. -co, --bazelCommandOptions= Additional space separated Bazel command options used when invoking Bazel + --contentHashPath= + Path to content hash json file. It's a map which maps + relative file path from workspace path to its + content hash. Files in this map will skip content + hashing and use provided value -h, --help Show this help message and exit. -k, --[no-]keep_going This flag controls if `bazel query` will be executed with the `--keep_going` flag or not. Disabling this diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt b/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt index 1df6a0e..0459156 100644 --- a/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt +++ b/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt @@ -41,10 +41,10 @@ class GenerateHashesCommand : Callable { lateinit var bazelPath: Path @CommandLine.Option( - names = ["--contentHashPath"], - description = ["Path to content hash json file. It's a map which maps relative file path from workspace path to its content hash. Files in this map will skip content hashing"], - scope = CommandLine.ScopeType.INHERIT, - required = false + names = ["--contentHashPath"], + description = ["Path to content hash json file. It's a map which maps relative file path from workspace path to its content hash. Files in this map will skip content hashing and use provided value"], + scope = CommandLine.ScopeType.INHERIT, + required = false ) var contentHashPath: Path? = null From 8546a1ccee18638e519408c08ad4a96b16fb26f6 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 18 May 2022 22:41:11 +1000 Subject: [PATCH 4/4] use existing deserialiser --- .../bazel_diff/cli/GenerateHashesCommand.kt | 14 +++++++++++++- .../main/kotlin/com/bazel_diff/di/Modules.kt | 3 ++- .../interactor/DeserialiseHashesInteractor.kt | 5 +++-- .../com/bazel_diff/io/ContentHashProvider.kt | 18 +++++++----------- .../bazel_diff/io/ContentHashProviderTest.kt | 19 ++++++++++--------- 5 files changed, 35 insertions(+), 24 deletions(-) diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt b/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt index 0459156..6e06968 100644 --- a/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt +++ b/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt @@ -46,7 +46,7 @@ class GenerateHashesCommand : Callable { scope = CommandLine.ScopeType.INHERIT, required = false ) - var contentHashPath: Path? = null + var contentHashPath: File? = null @CommandLine.Option( names = ["-so", "--bazelStartupOptions"], @@ -89,6 +89,7 @@ class GenerateHashesCommand : Callable { override fun call(): Int { val output = validateOutput(outputPath) + validate(contentHashPath=contentHashPath) startKoin { modules( @@ -118,4 +119,15 @@ class GenerateHashesCommand : Callable { "No output path specified." ) } + + private fun validate(contentHashPath: File?) { + contentHashPath?.let { + if (!it.canRead()) { + throw CommandLine.ParameterException( + spec.commandLine(), + "Incorrect contentHashFilePath: file doesn't exist or can't be read." + ) + } + } + } } diff --git a/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt b/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt index dbc3a33..08d044a 100644 --- a/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt +++ b/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt @@ -13,12 +13,13 @@ import com.google.gson.GsonBuilder import org.koin.core.module.Module import org.koin.core.qualifier.named import org.koin.dsl.module +import java.io.File import java.nio.file.Path fun hasherModule( workingDirectory: Path, bazelPath: Path, - contentHashPath: Path?, + contentHashPath: File?, startupOptions: List, commandOptions: List, keepGoing: Boolean?, diff --git a/cli/src/main/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractor.kt b/cli/src/main/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractor.kt index badde3b..d4be7db 100644 --- a/cli/src/main/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractor.kt +++ b/cli/src/main/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractor.kt @@ -1,6 +1,7 @@ package com.bazel_diff.interactor import com.google.gson.Gson +import com.google.gson.reflect.TypeToken import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.io.File @@ -13,7 +14,7 @@ class DeserialiseHashesInteractor : KoinComponent { * @param file path to file that has been pre-validated */ fun execute(file: File): Map { - val gsonHash: Map = HashMap() - return gson.fromJson(FileReader(file), gsonHash.javaClass) + val shape = object : TypeToken>() {}.type + return gson.fromJson(FileReader(file), shape) } } diff --git a/cli/src/main/kotlin/com/bazel_diff/io/ContentHashProvider.kt b/cli/src/main/kotlin/com/bazel_diff/io/ContentHashProvider.kt index d877e72..9041bff 100644 --- a/cli/src/main/kotlin/com/bazel_diff/io/ContentHashProvider.kt +++ b/cli/src/main/kotlin/com/bazel_diff/io/ContentHashProvider.kt @@ -1,18 +1,14 @@ package com.bazel_diff.io -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import java.nio.file.Files -import java.nio.file.Path +import com.bazel_diff.interactor.DeserialiseHashesInteractor +import java.io.File -class ContentHashProvider(path: Path?) { +class ContentHashProvider(file: File?) { // filename relative to workspace -> content hash of the file - val filenameToHash: Map? = if (path == null) null else readJson(path) + val filenameToHash: Map? = if (file == null) null else readJson(file) - private fun readJson(file: Path): Map { - val gson = Gson() - val reader = Files.newBufferedReader(file) - val shape = object : TypeToken>() {}.type - return gson.fromJson(reader, shape) + private fun readJson(file: File): Map { + val deserialiser = DeserialiseHashesInteractor() + return deserialiser.execute(file) } } diff --git a/cli/src/test/kotlin/com/bazel_diff/io/ContentHashProviderTest.kt b/cli/src/test/kotlin/com/bazel_diff/io/ContentHashProviderTest.kt index de02f38..6bfd7dd 100644 --- a/cli/src/test/kotlin/com/bazel_diff/io/ContentHashProviderTest.kt +++ b/cli/src/test/kotlin/com/bazel_diff/io/ContentHashProviderTest.kt @@ -9,7 +9,7 @@ import org.junit.Rule import org.junit.Test import org.koin.test.KoinTest import org.koin.test.KoinTestRule -import kotlin.io.path.Path +import java.io.File internal class ContentHashProviderTest: KoinTest { @get:Rule @@ -26,25 +26,26 @@ internal class ContentHashProviderTest: KoinTest { @Test fun testNonExistingPath() = runBlocking { assertThat { - ContentHashProvider(Path("/not/exists")) - }.isFailure().hasClass(java.nio.file.NoSuchFileException::class) + ContentHashProvider(File("/not/exists")) + }.isFailure().hasClass(java.io.FileNotFoundException::class) } @Test fun testParseJsonFileWithWrongShape() = runBlocking { - val path = Path("cli/src/test/kotlin/com/bazel_diff/io/fixture/wrong.json") + val file = File("cli/src/test/kotlin/com/bazel_diff/io/fixture/wrong.json") assertThat { - ContentHashProvider(path) + val a = ContentHashProvider(file) + println(a.filenameToHash) }.isFailure().hasClass(JsonSyntaxException::class) } @Test fun testParseJsonFileWithCorrectShape() = runBlocking { - val path = Path("cli/src/test/kotlin/com/bazel_diff/io/fixture/correct.json") - val map = ContentHashProvider(path).filenameToHash + val file = File("cli/src/test/kotlin/com/bazel_diff/io/fixture/correct.json") + val map = ContentHashProvider(file).filenameToHash assertThat(map).isNotNull().containsOnly( - "foo" to "content-hash-for-foo", - "bar" to "content-hash-for-bar" + "foo" to "content-hash-for-foo", + "bar" to "content-hash-for-bar" ) } }