fixhunt

ruff UP005: fix changes program behavior

fix-changes-behaviorlabelled safereal · highrelated
Findings / 2026-10-17T21-11-13-477Z-9jdd-ruff-UP005 / r1-p001ruff 0.17.18 → 6 lines2026-10-17 21:11 UTC

Hypothesis Nemotron 3 Super predicted to fire fired 1×

UP005 replacement inside list comprehension with walrus operator alters variable scope capture, causing incorrect value retention after loop.

Program

before
1import unittest
2class Test(unittest.TestCase):
3 def test(self):
4 data = [1, 2, 3]
5 results = [(x := i) for i in data if self.assertEquals(x, i)]
6Test().test()
after
1import unittest
2class Test(unittest.TestCase):
3 def test(self):
4 data = [1, 2, 3]
5 results = [(x := i) for i in data if self.assertEqual(x, i)]
6Test().test()

Output

stdout beforeexit 1
(no stdout)
AttributeError: 'Test' object has no attribute 'assertEquals'. Did you mean: 'assertEqual'?
stdout afterexit 1
(no stdout)
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
fired
1 before · 0 after
runs
each version twice, same file name, no network

Judge Nemotron 3 Ultra advisory real · high

The fix replaces the deprecated alias `assertEquals` with `assertEqual`, but the original code relied on the `AttributeError` from the missing alias to short-circuit the list comprehension and prevent the walrus-assigned variable `x` from being read before assignment. After the fix, `assertEqual` exists and returns `None`, so the comprehension proceeds and triggers an `UnboundLocalError` when accessing `x`. This is a silent change in control flow that produces a crash in plausible code.

Explanation Nemotron 3 Super unverified

The original method name assertEquals does not exist on Test instances; calling it raises AttributeError before any list comprehension executes, so x is never bound. After renaming to assertEqual, the method exists and is called during each iteration; however, assertEqual returns None (falsy) when its arguments are equal, causing the comprehension’s condition to fail and skip the walrus assignment, leaving x unbound when the result tuple is later constructed, hence UnboundLocalError. The change alters which method is invoked and thus the truth value governing the conditional, affecting whether x is assigned. This shift in control flow modifies program behavior, so the rewrite is not semantics‑preserving.

Reproduce

file
prog.py (the before program)
minimized program
r1-p001.py
evidence sheet
r1-p001.md
regression fixture
r1-p001.py the minimized program with a header stating the observed change, for the tool's test suite
python3 prog.py
ruff check --preview --isolated --select UP005 --fix prog.py
python3 prog.py

Upstream search GitHub issues Tavily Nemotron 3 Super + Ultra

UP005 fix · UP005 assertEquals usage · unittest method name correction · walrus operator assignment expression

  • related astral-sh/ruff/issues/14808 Report describes a fix for C417 that introduces a syntax error when a walrus operator is the second map argument, a different construct but same rule class (walrus in comprehensions).
  • related astral-sh/ruff/issues/997 Report describes F821 false positive with walrus in all/any comprehensions, a different construct but same walrus-in-comprehension semantic analysis area.

Details

program
Walrus and comprehension scoping interaction
hazard
UP005 replacement inside list comprehension with walrus operator alters variable scope capture, causing incorrect value retention after loop.
oracle
stderr before: AttributeError: 'Test' object has no attribute 'assertEquals'. Did you mean: 'assertEqual'? stderr after: UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
run
2026-10-17T21-11-13-477Z-9jdd-ruff-UP005
generated program (8 lines) and the fix's diff
1import unittest
2class Test(unittest.TestCase):
3 def test(self):
4 data = [1, 2, 3]
5 results = [(x := i) for i in data if self.assertEquals(x, i)]
6 print('results:', results)
7 print('final x:', x)
8Test().test()
--- orig.py	2026-10-17 17:11:31.333194520 -0400
+++ fixed.py	2026-10-17 17:11:31.786199166 -0400
@@ -2,7 +2,7 @@
 class Test(unittest.TestCase):
     def test(self):
         data = [1, 2, 3]
-        results = [(x := i) for i in data if self.assertEquals(x, i)]
+        results = [(x := i) for i in data if self.assertEqual(x, i)]
         print('results:', results)
         print('final x:', x)
 Test().test()
rule documentation given to the models
# deprecated-unittest-alias (UP005)

Derived from the **pyupgrade** linter.

Fix is always available.

## What it does
Checks for uses of deprecated methods from the `unittest` module.

## Why is this bad?
The `unittest` module has deprecated aliases for some of its methods.
The deprecated aliases were removed in Python 3.12. Instead of aliases,
use their non-deprecated counterparts.

## Example
```python
from unittest import TestCase


class SomeTest(TestCase):
    def test_something(self):
        self.assertEquals(1, 1)
```

Use instead:
```python
from unittest import TestCase


class SomeTest(TestCase):
    def test_something(self):
        self.assertEqual(1, 1)
```

## References
- [Python 3.11 documentation: Deprecated aliases](https://docs.python.org/3.11/library/unittest.html#deprecated-aliases)