diff --git a/.github/workflows/issue_checks.yml b/.github/workflows/issue_checks.yml new file mode 100644 index 00000000000..a621d12bfc8 --- /dev/null +++ b/.github/workflows/issue_checks.yml @@ -0,0 +1,51 @@ +# Contains jobs corresponding to issue related checks. + +name: Issue Checks + +on: + issues: + types: [closed, reopened] + +permissions: + issues: write + +jobs: + script_check: + name: Closed TODO Issue Check + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + + - name: Set up Bazel + uses: abhinavsingh/setup-bazel@v3 + with: + version: 4.0.0 + + - name: TODO Issue Resolved Check + id: todoIssueResolvedCheck + if: ${{ github.event.action == 'closed' }} + run: | + bazel run //scripts:todo_issue_resolved_check -- $(pwd) ${{ github.event.issue.number }} ${{ github.sha }} + + - name: Reopen Issue + if: failure() && steps.todoIssueResolvedCheck.outcome == 'failure' + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + gh issue reopen ${{ github.event.issue.number }} + + - name: Check for duplicate comment + id: duplicateCommentCheck + if: failure() && steps.todoIssueResolvedCheck.outcome == 'failure' + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + gh issue view ${{ github.event.issue.number }} --json comments --jq '.comments[-1].body' > $(pwd)/latest_comment.txt + bazel run //scripts:todo_issue_comment_check -- $(pwd) latest_comment.txt script_failures.txt + + - name: Add Comment + if: failure() && steps.duplicateCommentCheck.outcome == 'failure' + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + gh issue comment ${{ github.event.issue.number }} -F $(pwd)/script_failures.txt diff --git a/scripts/BUILD.bazel b/scripts/BUILD.bazel index 304c79e37fe..f83dd74319a 100644 --- a/scripts/BUILD.bazel +++ b/scripts/BUILD.bazel @@ -186,3 +186,21 @@ kt_jvm_binary( "//scripts/src/java/org/oppia/android/scripts/todo:todo_open_check_lib", ], ) + +kt_jvm_binary( + name = "todo_issue_resolved_check", + testonly = True, + main_class = "org.oppia.android.scripts.todo.TodoIssueResolvedCheckKt", + runtime_deps = [ + "//scripts/src/java/org/oppia/android/scripts/todo:todo_issue_resolved_check_lib", + ], +) + +kt_jvm_binary( + name = "todo_issue_comment_check", + testonly = True, + main_class = "org.oppia.android.scripts.todo.TodoIssueCommentCheckKt", + runtime_deps = [ + "//scripts/src/java/org/oppia/android/scripts/todo:todo_issue_comment_check_lib", + ], +) diff --git a/scripts/assets/todo_open_exemptions.textproto b/scripts/assets/todo_open_exemptions.textproto index a244894abf2..eb25d0afa2c 100644 --- a/scripts/assets/todo_open_exemptions.textproto +++ b/scripts/assets/todo_open_exemptions.textproto @@ -266,6 +266,27 @@ todo_open_exemption { line_number: 900 line_number: 901 } +todo_open_exemption { + exempted_file_path: "scripts/src/javatests/org/oppia/android/scripts/todo/TodoIssueResolvedCheckTest.kt" + line_number: 68 + line_number: 70 + line_number: 76 + line_number: 92 + line_number: 94 + line_number: 100 + line_number: 126 + line_number: 128 + line_number: 134 + line_number: 138 + line_number: 140 + line_number: 141 + line_number: 170 + line_number: 172 + line_number: 178 + line_number: 182 + line_number: 184 + line_number: 185 +} todo_open_exemption { exempted_file_path: "scripts/src/java/org/oppia/android/scripts/todo/TodoCollector.kt" line_number: 82 diff --git a/scripts/src/java/org/oppia/android/scripts/todo/BUILD.bazel b/scripts/src/java/org/oppia/android/scripts/todo/BUILD.bazel index ac6d7a4f351..a099106c2cb 100644 --- a/scripts/src/java/org/oppia/android/scripts/todo/BUILD.bazel +++ b/scripts/src/java/org/oppia/android/scripts/todo/BUILD.bazel @@ -28,3 +28,22 @@ kt_jvm_library( "//scripts/src/java/org/oppia/android/scripts/todo/model:todo", ], ) + +kt_jvm_library( + name = "todo_issue_resolved_check_lib", + testonly = True, + srcs = ["TodoIssueResolvedCheck.kt"], + visibility = ["//scripts:oppia_script_binary_visibility"], + deps = [ + ":todo_collector", + "//scripts/src/java/org/oppia/android/scripts/common:repository_file", + "//scripts/src/java/org/oppia/android/scripts/todo/model:todo", + ], +) + +kt_jvm_library( + name = "todo_issue_comment_check_lib", + testonly = True, + srcs = ["TodoIssueCommentCheck.kt"], + visibility = ["//scripts:oppia_script_binary_visibility"], +) diff --git a/scripts/src/java/org/oppia/android/scripts/todo/TodoIssueCommentCheck.kt b/scripts/src/java/org/oppia/android/scripts/todo/TodoIssueCommentCheck.kt new file mode 100644 index 00000000000..716ec42ebc1 --- /dev/null +++ b/scripts/src/java/org/oppia/android/scripts/todo/TodoIssueCommentCheck.kt @@ -0,0 +1,61 @@ +package org.oppia.android.scripts.todo + +import java.io.File + +/** + * Script for ensuring that the [IssueTodosResolvedCheck] failure comment is not the same as the + * latest comment of the closed issue. + * + * Usage: + * bazel run //scripts:todo_issue_comment_check -- + * + * + * Arguments: + * - path_to_directory_root: directory path to the root of the Oppia Android repository. + * - path_to_latest_comment_file: file path to the latest comment body. + * - path_to_script_failure_comment_file: file path to the script failure comment body. + * + * Example: + * bazel run //scripts:todo_issue_comment_check -- $(pwd) latest_comment.txt script_failures.txt + * + * NOTE TO DEVELOPERS: The script is executed in the CI enviornment. The CI workflow provides the + * file path to the latest comment body of the closed issue and the file path to the script failure + * comment body. + */ +fun main(vararg args: String) { + val repoPath = args[0] + + val latestCommentFilePath = args[1] + + val failureCommentFilePath = args[2] + + val permaLinkPrefix = "https://github.com/oppia/oppia-android/blob/" + + // Here, we are adding 40 to account for the commit SHA-1 hash length (for context: + // https://en.wikipedia.org/wiki/SHA-1). + val compareStartIndex = permaLinkPrefix.length + 40 + + val latestCommentContentList = File(repoPath, latestCommentFilePath).readText().trim().lines() + + val failureCommentContentList = File(repoPath, failureCommentFilePath).readText().trim().lines() + + if (latestCommentContentList.size != failureCommentContentList.size) { + throw Exception("NEW COMMENT SHOULD BE POSTED") + } + + if (latestCommentContentList.first() != failureCommentContentList.first()) { + throw Exception("NEW COMMENT SHOULD BE POSTED") + } + + for (index in 1 until latestCommentContentList.size) { + // The commit SHA can vary from workflow to workflow. This can make the permalinks different. + // Hence, we are comparing by the relative file path. + val latestCommentLineContent = latestCommentContentList[index].substring(compareStartIndex) + val failureCommentLineContent = failureCommentContentList[index].substring(compareStartIndex) + if (latestCommentLineContent != failureCommentLineContent) { + throw Exception("NEW COMMENT SHOULD BE POSTED") + } + } + + println("LATEST COMMENT IS SAME AS THE FAILURE COMMENT") +} diff --git a/scripts/src/java/org/oppia/android/scripts/todo/TodoIssueResolvedCheck.kt b/scripts/src/java/org/oppia/android/scripts/todo/TodoIssueResolvedCheck.kt new file mode 100644 index 00000000000..858d6301d0c --- /dev/null +++ b/scripts/src/java/org/oppia/android/scripts/todo/TodoIssueResolvedCheck.kt @@ -0,0 +1,105 @@ +package org.oppia.android.scripts.todo + +import org.oppia.android.scripts.todo.model.Todo +import java.io.File + +/** + * Script for ensuring that all TODOs of the closed issue are resolved. + * + * Usage: + * bazel run //scripts:todo_issue_resolved_check -- + * + * + * Arguments: + * - path_to_directory_root: directory path to the root of the Oppia Android repository. + * - closed_issue_number: issue number of the closed issue. + * - github_sha: sha of the latest commit on the develop branch. + * + * Example: + * bazel run //scripts:todo_issue_resolved_check -- $(pwd) + * 6 77ff8361b4bde52f695ceb91aa1aab36932a94fe + * + * NOTE TO DEVELOPERS: The script is executed in the CI enviornment. + */ +fun main(vararg args: String) { + // Path of the repo to be analyzed. + val repoPath = "${args[0]}/" + + // Issue number of the closed issue. + val closedIssueNumber = args[1] + + val commitSha = args[2] + + val githubPermalinkUrl = "https://github.com/oppia/oppia-android/blob/$commitSha" + + val allTodos = TodoCollector.collectCorrectlyFormattedTodos(TodoCollector.collectTodos(repoPath)) + + val todoIssueResolvedFailures = allTodos.filter { todo -> + checkIfTodoIssueResolvedFailure( + codeLine = todo.lineContent, + closedIssueNumber = closedIssueNumber + ) + } + + logFailures( + todoIssueResolvedFailures = todoIssueResolvedFailures, + failureMessage = "The following TODOs are unresolved for the closed issue:" + ) + + if (todoIssueResolvedFailures.isNotEmpty()) { + generateTodoListFile(repoPath, todoIssueResolvedFailures, githubPermalinkUrl) + throw Exception("TODO ISSUE RESOLVED CHECK FAILED") + } else { + println("TODO ISSUE RESOLVED CHECK PASSED") + } +} + +/** + * Checks whether a TODO corresponds to the closed issue. + * + * @param codeLine line content corresponding to the todo + * @param closedIssueNumber issue number of the closed issue + */ +private fun checkIfTodoIssueResolvedFailure(codeLine: String, closedIssueNumber: String): Boolean { + val parsedIssueNumberFromTodo = TodoCollector.parseIssueNumberFromTodo(codeLine) + return parsedIssueNumberFromTodo == closedIssueNumber +} + +/** + * Generates a file containing all the todos corresponding to the closed issue. + * + * @param repoPath path of the repo to be analyzed + * @param todoIssueResolvedFailures list of all the unresolved todos corresponding to the closed + * issue. + * @param githubPermalinkUrl the GitHub url for the permalinks + */ +private fun generateTodoListFile( + repoPath: String, + todoIssueResolvedFailures: List, + githubPermalinkUrl: String +) { + val todoListFile = File(repoPath + "script_failures.txt") + todoListFile.appendText("The issue is reopened because of the following unresolved TODOs:\n") + todoIssueResolvedFailures.sortedWith(compareBy({ it.filePath }, { it.lineNumber })) + .forEach { todo -> + todoListFile.appendText( + "$githubPermalinkUrl/${(todo.filePath).removePrefix(repoPath)}#L${todo.lineNumber}\n" + ) + } +} + +/** + * Logs the TODO issue resolved check failures. + * + * @param todoIssueResolvedFailures list of all the unresolved todos for the closed issue + * @param failureMessage the failure message to be logged + */ +private fun logFailures(todoIssueResolvedFailures: List, failureMessage: String) { + if (todoIssueResolvedFailures.isNotEmpty()) { + println(failureMessage) + todoIssueResolvedFailures.sortedWith(compareBy({ it.filePath }, { it.lineNumber })).forEach { + println("- ${it.filePath}:${it.lineNumber}") + } + println() + } +} diff --git a/scripts/src/javatests/org/oppia/android/scripts/todo/BUILD.bazel b/scripts/src/javatests/org/oppia/android/scripts/todo/BUILD.bazel index bd461afa28d..334e07b70f1 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/todo/BUILD.bazel +++ b/scripts/src/javatests/org/oppia/android/scripts/todo/BUILD.bazel @@ -25,3 +25,25 @@ kt_jvm_test( "//third_party:org_jetbrains_kotlin_kotlin-test-junit", ], ) + +kt_jvm_test( + name = "TodoIssueResolvedCheckTest", + srcs = ["TodoIssueResolvedCheckTest.kt"], + deps = [ + "//scripts/src/java/org/oppia/android/scripts/todo:todo_issue_resolved_check_lib", + "//testing:assertion_helpers", + "//third_party:com_google_truth_truth", + "//third_party:org_jetbrains_kotlin_kotlin-test-junit", + ], +) + +kt_jvm_test( + name = "TodoIssueCommentCheckTest", + srcs = ["TodoIssueCommentCheckTest.kt"], + deps = [ + "//scripts/src/java/org/oppia/android/scripts/todo:todo_issue_comment_check_lib", + "//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/todo/TodoIssueCommentCheckTest.kt b/scripts/src/javatests/org/oppia/android/scripts/todo/TodoIssueCommentCheckTest.kt new file mode 100644 index 00000000000..6c4d5fb8a8c --- /dev/null +++ b/scripts/src/javatests/org/oppia/android/scripts/todo/TodoIssueCommentCheckTest.kt @@ -0,0 +1,146 @@ +package org.oppia.android.scripts.todo + +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.testing.assertThrows +import java.io.ByteArrayOutputStream +import java.io.PrintStream + +/** Tests for [TodoIssueCommentCheck]. */ +class TodoIssueCommentCheckTest { + private val outContent: ByteArrayOutputStream = ByteArrayOutputStream() + private val originalOut: PrintStream = System.out + private val permalinkPrefix = "https://github.com/oppia/oppia-android/blob" + private val dummySha1 = "51ab6a0341cfb86d95a387438fc993b5eb977b83" + private val dummySha2 = "74cd6a0341cfb86d95a387438fc993b5eb977b83" + + @Rule + @JvmField + var tempFolder = TemporaryFolder() + + @Before + fun setUp() { + System.setOut(PrintStream(outContent)) + } + + @After + fun restoreStreams() { + System.setOut(originalOut) + } + + /** + * This is the case when no comment is present on the issue. In this case the GitHub API produces + * an empty comment body. + */ + @Test + fun testFailureComment_emptyLatestCommentBody_checkShouldFail() { + val latestCommentFile = tempFolder.newFile("latest_comment.txt") + val scriptFailureCommentFile = tempFolder.newFile("script_failures.txt") + val latestCommentContent = "" + val scriptFailureCommentContent = + """ + The issue is reopened because of the following unresolved TODOs: + $permalinkPrefix/$dummySha1/scripts/BUILD.bazel#L184 + $permalinkPrefix/$dummySha1/scripts/BUILD.bazel#L185 + + """.trimIndent() + latestCommentFile.writeText(latestCommentContent) + scriptFailureCommentFile.writeText(scriptFailureCommentContent) + + val exception = assertThrows(Exception::class) { + main(tempFolder.root.toString(), "latest_comment.txt", "script_failures.txt") + } + assertThat(exception).hasMessageThat().contains("NEW COMMENT SHOULD BE POSTED") + } + + @Test + fun testFailureComment_differentScriptFailures_withSameSha_checkShouldFail() { + val latestCommentFile = tempFolder.newFile("latest_comment.txt") + val scriptFailureCommentFile = tempFolder.newFile("script_failures.txt") + val latestCommentContent = + """ + The issue is reopened because of the following unresolved TODOs: + $permalinkPrefix/$dummySha1/scripts/BUILD.bazel#L184 + $permalinkPrefix/$dummySha1/scripts/BUILD.bazel#L185 + $permalinkPrefix/$dummySha1/scripts/BUILD.bazel#L186 + + + """.trimIndent() + val scriptFailureCommentContent = + """ + The issue is reopened because of the following unresolved TODOs: + $permalinkPrefix/$dummySha1/scripts/BUILD.bazel#L184 + $permalinkPrefix/$dummySha1/scripts/BUILD.bazel#L185 + + """.trimIndent() + latestCommentFile.writeText(latestCommentContent) + scriptFailureCommentFile.writeText(scriptFailureCommentContent) + + val exception = assertThrows(Exception::class) { + main(tempFolder.root.toString(), "latest_comment.txt", "script_failures.txt") + } + assertThat(exception).hasMessageThat().contains("NEW COMMENT SHOULD BE POSTED") + } + + @Test + fun testFailureComment_differentScriptFailures_withDifferentSha_checkShouldFail() { + val latestCommentFile = tempFolder.newFile("latest_comment.txt") + val scriptFailureCommentFile = tempFolder.newFile("script_failures.txt") + val latestCommentContent = + """ + The issue is reopened because of the following unresolved TODOs: + $permalinkPrefix/$dummySha1/scripts/BUILD.bazel#L184 + $permalinkPrefix/$dummySha1/scripts/BUILD.bazel#L185 + $permalinkPrefix/$dummySha1/scripts/BUILD.bazel#L186 + + + """.trimIndent() + val scriptFailureCommentContent = + """ + The issue is reopened because of the following unresolved TODOs: + $permalinkPrefix/$dummySha2/scripts/BUILD.bazel#L184 + $permalinkPrefix/$dummySha2/scripts/BUILD.bazel#L185 + + """.trimIndent() + latestCommentFile.writeText(latestCommentContent) + scriptFailureCommentFile.writeText(scriptFailureCommentContent) + + val exception = assertThrows(Exception::class) { + main(tempFolder.root.toString(), "latest_comment.txt", "script_failures.txt") + } + assertThat(exception).hasMessageThat().contains("NEW COMMENT SHOULD BE POSTED") + } + + @Test + fun testFailureComment_sameScriptFailures_withDifferentSha_checkShouldFail() { + val latestCommentFile = tempFolder.newFile("latest_comment.txt") + val scriptFailureCommentFile = tempFolder.newFile("script_failures.txt") + val latestCommentContent = + """ + The issue is reopened because of the following unresolved TODOs: + $permalinkPrefix/$dummySha1/scripts/BUILD.bazel#L184 + $permalinkPrefix/$dummySha1/scripts/BUILD.bazel#L185 + + + """.trimIndent() + val scriptFailureCommentContent = + """ + The issue is reopened because of the following unresolved TODOs: + $permalinkPrefix/$dummySha2/scripts/BUILD.bazel#L184 + $permalinkPrefix/$dummySha2/scripts/BUILD.bazel#L185 + + """.trimIndent() + latestCommentFile.writeText(latestCommentContent) + scriptFailureCommentFile.writeText(scriptFailureCommentContent) + + main(tempFolder.root.toString(), "latest_comment.txt", "script_failures.txt") + + assertThat(outContent.toString().trim()).isEqualTo( + "LATEST COMMENT IS SAME AS THE FAILURE COMMENT" + ) + } +} diff --git a/scripts/src/javatests/org/oppia/android/scripts/todo/TodoIssueResolvedCheckTest.kt b/scripts/src/javatests/org/oppia/android/scripts/todo/TodoIssueResolvedCheckTest.kt new file mode 100644 index 00000000000..f65c515dca2 --- /dev/null +++ b/scripts/src/javatests/org/oppia/android/scripts/todo/TodoIssueResolvedCheckTest.kt @@ -0,0 +1,209 @@ +package org.oppia.android.scripts.todo + +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.testing.assertThrows +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.PrintStream + +/** Tests for [TodoIssueResolvedCheck]. */ +class TodoIssueResolvedCheckTest { + private val outContent: ByteArrayOutputStream = ByteArrayOutputStream() + private val originalOut: PrintStream = System.out + private val CLOSED_ISSUE_CHECK_PASSED_OUTPUT_INDICATOR: String = + "TODO ISSUE RESOLVED CHECK PASSED" + private val CLOSED_ISSUE_CHECK_FAILED_OUTPUT_INDICATOR: String = + "TODO ISSUE RESOLVED CHECK FAILED" + + @Rule + @JvmField + var tempFolder = TemporaryFolder() + + @Before + fun setUp() { + tempFolder.newFolder("testfiles") + System.setOut(PrintStream(outContent)) + } + + @After + fun restoreStreams() { + System.setOut(originalOut) + } + + @Test + fun testClosedIssueCheck_noTodosPresent_checkShouldPass() { + val tempFile1 = tempFolder.newFile("testfiles/TempFile1.kt") + val tempFile2 = tempFolder.newFile("testfiles/TempFile2.bazel") + val testContent1 = + """ + // Test comment 1 + + // Test comment 2 + """.trimIndent() + val testContent2 = + """ + # test comment 3 + # test todo which is not a todo + # test comment 4 + """.trimIndent() + tempFile1.writeText(testContent1) + tempFile2.writeText(testContent2) + + main(retrieveTestFilesDirectoryPath(), "1200", "abmzuyt") + + assertThat(outContent.toString().trim()).isEqualTo(CLOSED_ISSUE_CHECK_PASSED_OUTPUT_INDICATOR) + } + + @Test + fun testClosedIssueCheck_noTodoCorrespondsToClosedIssue_checkShouldPass() { + val tempFile1 = tempFolder.newFile("testfiles/TempFile1.kt") + val tempFile2 = tempFolder.newFile("testfiles/TempFile2.bazel") + val testContent1 = + """ + // TODO(#169877): test description 1 + + // TODO(#1021211): test description 2 + """.trimIndent() + val testContent2 = + """ + # test comment 3 + # test todo which is not a todo + # TODO(#1400000): test description 3 + """.trimIndent() + tempFile1.writeText(testContent1) + tempFile2.writeText(testContent2) + + main(retrieveTestFilesDirectoryPath(), "1200", "abmzuyt") + + assertThat(outContent.toString().trim()).isEqualTo(CLOSED_ISSUE_CHECK_PASSED_OUTPUT_INDICATOR) + } + + @Test + fun testClosedIssueCheck_todosCorrespondsToClosedIssue_checkShouldFail() { + val tempFile1 = tempFolder.newFile("testfiles/TempFile1.kt") + val tempFile2 = tempFolder.newFile("testfiles/TempFile2.bazel") + val testContent1 = + """ + // TODO(#169877): test description 1 + + // TODO(#1021211): test description 2 + """.trimIndent() + val testContent2 = + """ + # test comment 3 + # test todo which is not a todo + # TODO(#169877): test description 3 + """.trimIndent() + tempFile1.writeText(testContent1) + tempFile2.writeText(testContent2) + + val exception = assertThrows(Exception::class) { + main(retrieveTestFilesDirectoryPath(), "169877", "abmzuyt") + } + + assertThat(exception).hasMessageThat().contains(CLOSED_ISSUE_CHECK_FAILED_OUTPUT_INDICATOR) + val failureMessage = + """ + The following TODOs are unresolved for the closed issue: + - ${retrieveTestFilesDirectoryPath()}/TempFile1.kt:1 + - ${retrieveTestFilesDirectoryPath()}/TempFile2.bazel:3 + """.trimIndent() + assertThat(outContent.toString().trim()).isEqualTo(failureMessage) + } + + @Test + fun testClosedIssueCheck_todosCorrespondsToClosedIssue_logsShouldBeLexicographicallySorted() { + val tempFile3 = tempFolder.newFile("testfiles/TempFile3.xml") + val tempFile2 = tempFolder.newFile("testfiles/TempFile2.bazel") + val tempFile1 = tempFolder.newFile("testfiles/TempFile1.kt") + val testContent1 = + """ + // TODO(#169877): test description 1 + + // TODO(#1021211): test description 2 + """.trimIndent() + val testContent2 = + """ + # test comment 3 + # test todo which is not a todo + # TODO(#169877): test description 3 + """.trimIndent() + val testContent3 = + """ + + + + + """.trimIndent() + tempFile1.writeText(testContent1) + tempFile2.writeText(testContent2) + tempFile3.writeText(testContent3) + + val exception = assertThrows(Exception::class) { + main(retrieveTestFilesDirectoryPath(), "169877", "abmzuyt") + } + + assertThat(exception).hasMessageThat().contains(CLOSED_ISSUE_CHECK_FAILED_OUTPUT_INDICATOR) + val failureMessage = + """ + The following TODOs are unresolved for the closed issue: + - ${retrieveTestFilesDirectoryPath()}/TempFile1.kt:1 + - ${retrieveTestFilesDirectoryPath()}/TempFile2.bazel:3 + - ${retrieveTestFilesDirectoryPath()}/TempFile3.xml:1 + - ${retrieveTestFilesDirectoryPath()}/TempFile3.xml:4 + """.trimIndent() + assertThat(outContent.toString().trim()).isEqualTo(failureMessage) + } + + @Test + fun testClosedIssueCheck_triggerFailure_generatedFileContentShouldContainFailureTodoList() { + val tempFile3 = tempFolder.newFile("testfiles/TempFile3.xml") + val tempFile2 = tempFolder.newFile("testfiles/TempFile2.bazel") + val tempFile1 = tempFolder.newFile("testfiles/TempFile1.kt") + val testContent1 = + """ + // TODO(#169877): test description 1 + + // TODO(#1021211): test description 2 + """.trimIndent() + val testContent2 = + """ + # test comment 3 + # test todo which is not a todo + # TODO(#169877): test description 3 + """.trimIndent() + val testContent3 = + """ + + + + + """.trimIndent() + tempFile1.writeText(testContent1) + tempFile2.writeText(testContent2) + tempFile3.writeText(testContent3) + + val exception = assertThrows(Exception::class) { + main(retrieveTestFilesDirectoryPath(), "169877", "abmzuyt") + } + val fileContentList = + File("${retrieveTestFilesDirectoryPath()}/script_failures.txt").readLines() + assertThat(fileContentList).containsExactly( + "The issue is reopened because of the following unresolved TODOs:", + "https://github.com/oppia/oppia-android/blob/abmzuyt/TempFile1.kt#L1", + "https://github.com/oppia/oppia-android/blob/abmzuyt/TempFile2.bazel#L3", + "https://github.com/oppia/oppia-android/blob/abmzuyt/TempFile3.xml#L1", + "https://github.com/oppia/oppia-android/blob/abmzuyt/TempFile3.xml#L4" + ).inOrder() + } + + /** Retrieves the absolute path of testfiles directory. */ + private fun retrieveTestFilesDirectoryPath(): String { + return "${tempFolder.root}/testfiles" + } +}