diff --git a/scripts/src/javatests/org/oppia/android/scripts/ci/BUILD.bazel b/scripts/src/javatests/org/oppia/android/scripts/ci/BUILD.bazel index fa1a3c2798a..bd50cdfa588 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/ci/BUILD.bazel +++ b/scripts/src/javatests/org/oppia/android/scripts/ci/BUILD.bazel @@ -31,3 +31,17 @@ kt_jvm_test( "//third_party:org_jetbrains_kotlin_kotlin-test-junit", ], ) + +kt_jvm_test( + name = "RetrieveChangedFilesTest", + srcs = ["RetrieveChangedFilesTest.kt"], + deps = [ + "//scripts:test_file_check_assets", + "//scripts/src/java/org/oppia/android/scripts/ci:retrieve_changed_files_lib", + "//scripts/src/java/org/oppia/android/scripts/common:proto_string_encoder", + "//scripts/src/java/org/oppia/android/scripts/testing:test_bazel_workspace", + "//testing:assertion_helpers", + "//third_party:com_google_truth_truth", + "//third_party:org_jetbrains_kotlin_kotlin-test-junit", + ], +) diff --git a/scripts/src/javatests/org/oppia/android/scripts/ci/RetrieveChangedFilesTest.kt b/scripts/src/javatests/org/oppia/android/scripts/ci/RetrieveChangedFilesTest.kt new file mode 100644 index 00000000000..d6a3bb2ead7 --- /dev/null +++ b/scripts/src/javatests/org/oppia/android/scripts/ci/RetrieveChangedFilesTest.kt @@ -0,0 +1,139 @@ +package org.oppia.android.scripts.ci + +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.oppia.android.scripts.common.CommandExecutor +import org.oppia.android.scripts.common.CommandExecutorImpl +import org.oppia.android.scripts.common.ProtoStringEncoder.Companion.toCompressedBase64 +import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher +import org.oppia.android.scripts.proto.ChangedFilesBucket +import org.oppia.android.scripts.testing.TestBazelWorkspace +import org.oppia.android.testing.assertThrows +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.OutputStream +import java.io.PrintStream +import java.util.concurrent.TimeUnit + +/** Tests for the retrieve_changed_files utility. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +class RetrieveChangedFilesTest { + @field:[Rule JvmField] val tempFolder = TemporaryFolder() + + private val scriptBgDispatcher by lazy { ScriptBackgroundCoroutineDispatcher() } + private lateinit var commandExecutor: CommandExecutor + private lateinit var testBazelWorkspace: TestBazelWorkspace + private lateinit var pendingOutputStream: ByteArrayOutputStream + private lateinit var originalStandardOutputStream: OutputStream + + @Before + fun setUp() { + commandExecutor = initializeCommandExecutorWithLongProcessWaitTime() + testBazelWorkspace = TestBazelWorkspace(tempFolder) + + // Redirect script output for testing purposes. + pendingOutputStream = ByteArrayOutputStream() + originalStandardOutputStream = System.out + System.setOut(PrintStream(pendingOutputStream)) + } + + @After + fun tearDown() { + // Reinstate test output redirection. + System.setOut(PrintStream(pendingOutputStream)) + + // Print the status of the git repository to help with debugging in the cases of test failures + // and to help manually verify the expect git state at the end of each test. + println("git status (at end of test):") + + scriptBgDispatcher.close() + } + + @Test + fun testUtility_noArguments_printsUsageStringAndExits() { + val exception = assertThrows() { runScript() } + + // Bazel catches the System.exit() call and throws a SecurityException. This is a bit hacky way + // to verify that System.exit() is called, but it's helpful. + assertThat(exception).hasMessageThat().contains("System.exit()") + assertThat(pendingOutputStream.toString()).contains("Usage:") + } + + @Test + fun testUtility_invalidArguments_printsUsageStringAndExits() { + for (argCount in 0..4) { + val args = Array(argCount) { "arg${it + 1}" } + val exception = assertThrows { runScript(*args) } + + // Bazel catches the System.exit() call and throws a SecurityException. + assertThat(exception).hasMessageThat().contains("System.exit()") + assertThat(pendingOutputStream.toString()).contains("Usage:") + } + } + + @Test + fun testUtility_invalidBase64_throwsException() { + assertThrows() { runScript("${tempFolder.root}", "badbase64", "file1", "file2", "file3") } + } + + @Test + fun testUtility_validBase64_oneTest_writesCacheNameFile() { + val cacheNameFilePath = tempFolder.getNewTempFilePath("cache_name") + val changedFilePath = tempFolder.getNewTempFilePath("changed_file_list") + val testTargetFilePath = tempFolder.getNewTempFilePath("test_target_list") + val base64String = computeBase64String( + ChangedFilesBucket.newBuilder().apply { + cacheBucketName = "example" + addChangedFiles("//example/to/a/file/Demonstration.kt") + }.build() + ) + + runScript(tempFolder.root.absolutePath, base64String, cacheNameFilePath, changedFilePath, testTargetFilePath) + + assertThat(File(cacheNameFilePath).readText().trim()).isEqualTo("example") + } + + @Test + fun testUtility_validBase64_oneTest_writesChangedFilePathWithCorrectFile() { + val cacheNameFilePath = tempFolder.getNewTempFilePath("cache_name") + val changedFilePath = tempFolder.getNewTempFilePath("changed_file_list") + val testTargetFilePath = tempFolder.getNewTempFilePath("test_target_list") + val base64String = computeBase64String( + ChangedFilesBucket.newBuilder().apply { + cacheBucketName = "example" + addChangedFiles("//example/to/a/file/Demonstration.kt") + }.build() + ) + + runScript(tempFolder.root.absolutePath, base64String, cacheNameFilePath, changedFilePath, testTargetFilePath) + + assertThat(File(changedFilePath).readText().trim()).isEqualTo( + "//example/to/a/file/Demonstration.kt" + ) + } + + private fun runScript(vararg args: String) { + main(args.toList().toTypedArray()) + } + + private fun computeBase64String(changedFilesBucket: ChangedFilesBucket): String = + changedFilesBucket.toCompressedBase64() + + /** + * Returns the absolute file path of a new file that can be written under this [TemporaryFolder] + * (but does not create the file). + */ + private fun TemporaryFolder.getNewTempFilePath(name: String) = + File(tempFolder.root, name).absolutePath + + private fun initializeCommandExecutorWithLongProcessWaitTime(): CommandExecutorImpl { + return CommandExecutorImpl( + scriptBgDispatcher, processTimeout = 5, processTimeoutUnit = TimeUnit.MINUTES + ) + } +} \ No newline at end of file