Repository: oppia/oppia
Pull request #22063: Modifed test in general_purpose_linter_test
Author: login: Helper2020
Labels: name: PR: LGTM
Created: 2025-03-05T21:51:33Z | Merged: 2025-03-08T04:01:54Z

## Overview

<!--
READ ME FIRST:
Please answer *all* questions below and check off every point from the Essential Checklist!
If there is no corresponding issue number, fill in N/A where it says [fill_in_number_here] below in 1.
-->

1. This PR fixes or fixes part of #N/A].
2. This PR does the following: [Black formatter (#21909 ) modifies the invalid_no_newline.py file, inadvertently adding a newline and causing test failures. To resolve this, the PR removes the file from the codebase while ensuring tests still pass by creating a temporary file that meets the necessary testing parameters.

## Essential Checklist

Please follow the [instructions for making a code change](https://github.com/oppia/oppia/wiki/Make-a-pull-request).

- [ ] I have linked the issue that this PR fixes in the "Development" section of the sidebar.
- [X ] I have checked the "Files Changed" tab and confirmed that the changes are what I want to make.
- [ ] I have written tests for my code.
- [ ] The **PR title** starts with "Fix #bugnum: " or "Fix part of #bugnum: ...", followed by a short, clear summary of the changes.
- [ X] I have assigned the correct reviewers to this PR (or will leave a comment with the phrase "@{{reviewer_username}} PTAL" if I can't assign them directly).


## Proof that changes are correct

<!--
Add before-and-after videos/screenshots of the user-facing interface (including
the browser devtools console) to demonstrate that the changes made in this PR
work correctly. Make sure the actions taken in the before and after videos are
the same. (For changes involving responsiveness or adjustment of UI elements,
this should be a video that gradually resizes the viewport from wide to narrow
and back again.) If this PR is for a developer-facing feature, provide
videos/screenshots of the developer-facing interface instead.

When you make updates to the PR, please update these videos/screenshots as well.
You can drop videos/screenshots from previous versions of the PR.

The above should be done for all PRs, including short ones (e.g. a single-line change).
However, if the changes in your PRs are autogenerated via a script and you cannot
provide proof for the changes then please leave a comment "No proof of changes
needed because {{Reason}}" and remove all the sections below.
-->




Diff:
diff --git a/scripts/linters/general_purpose_linter_test.py b/scripts/linters/general_purpose_linter_test.py
index 77297d55d7ca9..35ca0760cebb1 100644
--- a/scripts/linters/general_purpose_linter_test.py
+++ b/scripts/linters/general_purpose_linter_test.py
@@ -21,7 +21,7 @@
 import multiprocessing
 import os
 import re
-
+import tempfile
 from core.tests import test_utils
 
 from typing import Final, Pattern, Tuple
@@ -68,8 +68,6 @@
 INVALID_REQUEST_FILEPATH: Final = os.path.join(
     LINTER_TESTS_DIR, 'invalid_request.py'
 )
-INVALID_NO_NEWLINE_FILEPATH: Final = os.path.join(
-    LINTER_TESTS_DIR, 'invalid_no_newline.py')
 INVALID_URLOPEN_FILEPATH: Final = os.path.join(
     LINTER_TESTS_DIR, 'invalid_urlopen.py')
 INVALID_AUTHOR_FILEPATH: Final = os.path.join(
@@ -99,6 +97,25 @@
 VALID_PY_FILE_PATH = os.path.join(
     LINTER_TESTS_DIR, 'valid.py')
 
+INVALID_NO_NEWLINE_FILE_CONTENT = """from __future__ import annotations
+
+class FakeClass:
+    \"\"\"Fake docstring for valid syntax purposes.\"\"\"
+
+    def __init__(self, fake_arg):
+        self.fake_arg = fake_arg
+
+    def fake_method(self, name):
+        \"\"\"This doesn't do anything.
+
+        Args:
+            name: str. Means nothing.
+
+        Yields:
+            tuple(str, str).
+        \"\"\"
+        yield (name, name)"""
+
 
 class HTMLLintTests(test_utils.LinterTestBase):
     """Test the HTML lint functions."""
@@ -326,14 +343,22 @@ def test_linter_with_no_files(self) -> None:
         self.assertFalse(lint_task_report[0].failed)
 
     def test_file_with_no_newline_at_eof(self) -> None:
+        temp_file = tempfile.NamedTemporaryFile(
+            mode='w+', suffix='.py', delete=False)
+
+        # We use a temporary file here instead of a real one because
+        # the Black formatter auto-fixes newlines at the end of files.
+        temp_file.write(INVALID_NO_NEWLINE_FILE_CONTENT)
+        temp_file.close()
         linter = general_purpose_linter.GeneralPurposeLinter(
-            [INVALID_NO_NEWLINE_FILEPATH], FILE_CACHE)
+            [temp_file.name], FILE_CACHE)
         lint_task_report = linter.check_newline_at_eof()
         self.assert_same_list_elements(
             ['There should be a single newline at the end of file.'],
             lint_task_report.trimmed_messages)
         self.assertEqual('Newline at EOF', lint_task_report.name)
         self.assertTrue(lint_task_report.failed)
+        self.addCleanup(temp_file.close)
 
     def test_file_with_newline_at_eof(self) -> None:
         linter = general_purpose_linter.GeneralPurposeLinter(
@@ -393,7 +418,7 @@ def mock_is_filepath_excluded_for_bad_patterns_check(
 
         with filepath_excluded_swap:
             linter = general_purpose_linter.GeneralPurposeLinter(
-                [INVALID_NO_NEWLINE_FILEPATH], FILE_CACHE)
+                [INVALID_MERGE_CONFLICT_FILEPATH], FILE_CACHE)
             lint_task_report = linter.check_bad_patterns()
         self.assertEqual(
             ['SUCCESS  Bad pattern check passed'],
@@ -403,7 +428,7 @@ def mock_is_filepath_excluded_for_bad_patterns_check(
 
     def test_perform_all_lint_checks_with_success(self) -> None:
         linter = general_purpose_linter.GeneralPurposeLinter(
-            [INVALID_NO_NEWLINE_FILEPATH], FILE_CACHE)
+            [INVALID_MERGE_CONFLICT_FILEPATH], FILE_CACHE)
         lint_task_report = linter.perform_all_lint_checks()
         self.assertTrue(isinstance(lint_task_report, list))
 
diff --git a/scripts/linters/test_files/invalid_no_newline.py b/scripts/linters/test_files/invalid_no_newline.py
deleted file mode 100644
index 0c03a145988d6..0000000000000
--- a/scripts/linters/test_files/invalid_no_newline.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# coding: utf-8
-#
-# Copyright 2020 The Oppia Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS-IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Python file with invalid syntax, used by scripts/linters/
-python_linter_test.py. This file contain valid python syntax.
-"""
-
-from __future__ import annotations
-
-
-class FakeClass:
-    """This is a fake docstring for valid syntax purposes."""
-
-    def __init__(self, fake_arg):
-        self.fake_arg = fake_arg
-
-    def fake_method(self, name):
-        """This doesn't do anything.
-
-        Args:
-            name: str. Means nothing.
-
-        Yields:
-            tuple(str, str). The argument passed in but twice in a tuple.
-        """
-        yield (name, name)
\ No newline at end of file


Reviews:
user: login: seanlip
body: @Helper2020 Thanks! Left some comments, PTAL.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-07T16:07:42Z
user: login: Helper2020
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-08T00:09:53Z
user: login: seanlip
body: Thanks @Helper2020, LGTM! 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-08T03:03:37Z

Review comments:
diff_hunk: @@ -392,8 +416,11 @@ def mock_is_filepath_excluded_for_bad_patterns_check(
             mock_is_filepath_excluded_for_bad_patterns_check)
 
         with filepath_excluded_swap:
+            # Modified to pass tests; original file was invalid_no_newline.py.
path: scripts/linters/general_purpose_linter_test.py
user: login: seanlip
body: You do not need this comment. As long as the code is correct and makes sense, it is not an issue that the file under lint was changed from one invalid file to another.
created_at: 2025-03-07T16:04:42Z
author_association: MEMBER
diff_hunk: @@ -402,8 +429,11 @@ def mock_is_filepath_excluded_for_bad_patterns_check(
         self.assertFalse(lint_task_report.failed)
 
     def test_perform_all_lint_checks_with_success(self) -> None:
+            # Modified to pass tests; original file was invalid_no_newline.py.
path: scripts/linters/general_purpose_linter_test.py
user: login: seanlip
body: Ditto.
created_at: 2025-03-07T16:04:49Z
author_association: MEMBER
diff_hunk: @@ -326,14 +324,40 @@ def test_linter_with_no_files(self) -> None:
         self.assertFalse(lint_task_report[0].failed)
 
     def test_file_with_no_newline_at_eof(self) -> None:
+        temp_file = tempfile.NamedTemporaryFile(
+            mode='w+', suffix='.py', delete=False)
+
+        content = """from __future__ import annotations
path: scripts/linters/general_purpose_linter_test.py
user: login: seanlip
body: Note that this only doesn't have a newline at the end of the string "by chance" -- the last line of the file is actually the string "                        " (line 349).

Could you please reformat this similar to what is done in core/controllers/editor.py (around line 370) so that this mimics an actual Python file?

If you like, you can also move this constant up to the top of this file and call it INVALID_NO_NEWLINE_FILE_CONTENT. That might be a little cleaner. You can also leave a content above the `temp_file.write(content)` line below explaining that we use a temporary file here rather than an actual one because the Black formatter auto-fixes newlines at the end of files in LINTER_TESTS_DIR when it is run.
created_at: 2025-03-07T16:07:29Z
author_association: MEMBER
diff_hunk: @@ -326,14 +324,40 @@ def test_linter_with_no_files(self) -> None:
         self.assertFalse(lint_task_report[0].failed)
 
     def test_file_with_no_newline_at_eof(self) -> None:
+        temp_file = tempfile.NamedTemporaryFile(
+            mode='w+', suffix='.py', delete=False)
+
+        content = """from __future__ import annotations
path: scripts/linters/general_purpose_linter_test.py
user: login: Helper2020
body: Done.
created_at: 2025-03-08T00:09:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -392,8 +416,11 @@ def mock_is_filepath_excluded_for_bad_patterns_check(
             mock_is_filepath_excluded_for_bad_patterns_check)
 
         with filepath_excluded_swap:
+            # Modified to pass tests; original file was invalid_no_newline.py.
path: scripts/linters/general_purpose_linter_test.py
user: login: Helper2020
body: Done
created_at: 2025-03-08T00:09:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -402,8 +429,11 @@ def mock_is_filepath_excluded_for_bad_patterns_check(
         self.assertFalse(lint_task_report.failed)
 
     def test_perform_all_lint_checks_with_success(self) -> None:
+            # Modified to pass tests; original file was invalid_no_newline.py.
path: scripts/linters/general_purpose_linter_test.py
user: login: Helper2020
body: Done.
created_at: 2025-03-08T00:09:46Z
author_association: CONTRIBUTOR

PR comments:
user: login: oppiabot[bot]
created_at: 2025-03-05T21:51:45Z
body: Hi @Helper2020 please assign the required reviewer(s) for this PR. Thanks!
author_association: NONE
user: login: Helper2020
created_at: 2025-03-05T22:31:41Z
body: @seanlip  PTAL
author_association: CONTRIBUTOR
user: login: oppiabot[bot]
created_at: 2025-03-05T22:34:45Z
body: Unassigning @Helper2020 since a re-review was requested. @Helper2020, please make sure you have addressed all review comments. Thanks!
author_association: NONE
user: login: oppiabot[bot]
created_at: 2025-03-08T03:06:42Z
body: Hi @Helper2020, this PR is ready to be merged. Please address any remaining comments prior to merging, and feel free to ask someone to merge your PR once the CI checks pass and you're happy with it. Thanks!
author_association: NONE
user: login: oppia-github-app[bot]
created_at: 2025-03-08T05:05:30Z
body: Congratulations! This PR has been deployed to the test server. You can test it out at [this link](https://www.oppiatestserver.org/).
author_association: NONE