Repository: alexmojaki/stack_data

Issue:
0.6.2: Test case fail when pygments >= 2.16 
When building your module with pygments >= 2.16.0, the test cases will fail. 

Here is the pytest output：

<details>
  <summary>details</summary>

```shell
  + /usr/bin/python3 -m tox --current-env -q --recreate -e py311
  ============================= test session starts ==============================
  platform linux -- Python 3.11.4, pytest-7.4.0, pluggy-1.3.0
  cachedir: .tox/py311/.pytest_cache
  rootdir: /builddir/build/BUILD/stack_data-0.6.2
  collected 21 items
  tests/test_core.py ...............                                       [ 71%]
  tests/test_formatter.py F.                                               [ 80%]
  tests/test_serializer.py F                                               [ 85%]
  tests/test_utils.py ...                                                  [100%]
  =================================== FAILURES ===================================
  _________________________________ test_example _________________________________
  capsys = <_pytest.capture.CaptureFixture object at 0x7f7f6dff2750>
      def test_example(capsys):
          from .samples.formatter_example import bar, print_stack1, format_stack1, format_frame, f_string, blank_lines
      
          @contextmanager
          def check_example(name):
              yield
              stderr = capsys.readouterr().err
              compare_to_file(stderr, name)
      
          with check_example("variables"):
              try:
                  bar()
              except Exception:
                  MyFormatter(show_variables=True).print_exception()
      
  >       with check_example("pygmented"):
  tests/test_formatter.py:47: 
  _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
  /usr/lib64/python3.11/contextlib.py:144: in __exit__
      next(self.gen)
  tests/test_formatter.py:39: in check_example
      compare_to_file(stderr, name)
  _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
  text = 'Traceback (most recent call last):\n File "formatter_example.py", line 21, in foo\n       9 | \x1b[38;5;15m\x1b[39m\x...Error\x1b[39m\x1b[38;5;15m \x1b[39m\x1b[38;5;204mfrom\x1b[39m\x1b[38;5;15m \x1b[39m\x1b[38;5;15me\x1b[39m\nTypeError\n'
  name = 'pygmented'
      def compare_to_file(text, name):
          filename = os.path.join(
              os.path.dirname(__file__),
              'golden_files',
              name + '.txt',
          )
          if os.environ.get('FIX_STACK_DATA_TESTS'):
              string_to_file(text, filename)
          else:
              expected_output = file_to_string(filename)
  >           assert text == expected_output
  E           AssertionError
  tests/utils.py:16: AssertionError
  _________________________________ test_example _________________________________
      def test_example():
          from .samples.formatter_example import bar, format_frame, format_stack1
      
          result = dict(
              format_frame=(format_frame(MyFormatter())),
              format_stack=format_stack1(MyFormatter(show_variables=True)),
          )
      
          try:
              bar()
          except Exception:
              result.update(
                  plain=MyFormatter(show_variables=True).format_exception(),
                  pygmented=MyFormatter(show_variables=True, pygmented=True).format_exception(),
                  pygmented_html=MyFormatter(show_variables=True, pygmented=True, html=True).format_exception(),
              )
      
      
  >       compare_to_file_json(result, "serialize")
  tests/test_serializer.py:42: 
  _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
  data = {'format_frame': {'filename': 'formatter_example.py', 'lineno': 51, 'lines': [{'is_current': False, 'lineno': 49, 'tex...ormatter_example.py', 'lineno': 8, 'lines': [{...}, {...}, {...}, {...}], 'name': 'foo', ...}, ...], 'tail': ''}], ...}
  name = 'serialize'
      def compare_to_file_json(data, name):
          filename = os.path.join(
              os.path.dirname(__file__),
              'golden_files',
              name + '.json',
          )
          if os.environ.get('FIX_STACK_DATA_TESTS'):
              json_to_file(data, filename, indent=4)
          else:
              expected_output = file_to_json(filename)
  >           assert data == expected_output
  E           AssertionError
  tests/utils.py:29: AssertionError
  =============================== warnings summary ===============================
  ../../../../usr/lib64/python3.11/site-packages/pyximport/pyximport.py:51
    /usr/lib64/python3.11/site-packages/pyximport/pyximport.py:51: DeprecationWarning: the imp module is deprecated in favour of importlib and slated for removal in Python 3.12; see the module's documentation for alternative uses
      import imp
  -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
  =========================== short test summary info ============================
  FAILED tests/test_formatter.py::test_example - AssertionError
  FAILED tests/test_serializer.py::test_example - AssertionError
  =================== 2 failed, 19 passed, 1 warning in 1.02s ====================
  ERROR: InvocationError for command /usr/bin/pytest (exited with code 1)
  ___________________________________ summary ____________________________________
  ERROR:   py311: commands failed
```

</details>

I found that both of these failures use Monokai's color styles and have been [updated](https://github.com/pygments/pygments/commit/d46519b340760cd4f8978d7f1349f8c287121a24) since pygments-2.16.0

I hope to be able to update the test cases to support the latest pygments, maybe you would want to support both versions of pygments? 

Please let me know whether it is needed to support old version of pygments and I'll try to open a PR soon.


Discussion:

Patch:
diff --git a/stack_data/utils.py b/stack_data/utils.py
index 78ce2d6..ad8cd38 100644
--- a/stack_data/utils.py
+++ b/stack_data/utils.py
@@ -92,12 +92,13 @@ def is_frame(frame_or_tb: Union[FrameType, TracebackType]) -> bool:
 
 
 def iter_stack(frame_or_tb: Union[FrameType, TracebackType]) -> Iterator[Union[FrameType, TracebackType]]:
-    while frame_or_tb:
-        yield frame_or_tb
-        if is_frame(frame_or_tb):
-            frame_or_tb = frame_or_tb.f_back
+    current: Union[FrameType, TracebackType, None] = frame_or_tb
+    while current:
+        yield current
+        if is_frame(current):
+            current = current.f_back
         else:
-            frame_or_tb = frame_or_tb.tb_next
+            current = current.tb_next
 
 
 def frame_and_lineno(frame_or_tb: Union[FrameType, TracebackType]) -> Tuple[FrameType, int]:
diff --git a/tox.ini b/tox.ini
index b613d58..84e4ccb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -6,3 +6,4 @@ commands = pytest {posargs}
 extras = tests
 passenv =
     STACK_DATA_SLOW_TESTS
+    FIX_STACK_DATA_TESTS


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
index fe28111..e61d09f 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,7 +1,10 @@
 import os
 
 import pyximport
-from typeguard.importhook import install_import_hook
+try:
+    from typeguard import install_import_hook
+except ImportError:
+    from typeguard.importhook import install_import_hook
 
 pyximport.install(language_level=3)
 
diff --git a/tests/golden_files/pygmented.txt b/tests/golden_files/pygmented.txt
index 920dabc..c82f142 100644
--- a/tests/golden_files/pygmented.txt
+++ b/tests/golden_files/pygmented.txt
@@ -1,11 +1,11 @@
 Traceback (most recent call last):
  File "formatter_example.py", line 21, in foo
-       9 | [38;5;15m[39m[38;5;15mx[39m[38;5;15m [39m[38;5;197m=[39m[38;5;15m [39m[38;5;141m1[39m
-      10 | [38;5;15m[39m[38;5;15mlst[39m[38;5;15m [39m[38;5;197m=[39m[38;5;15m [39m[38;5;15m([39m
+       9 | [38;5;15m[39m[38;5;15mx[39m[38;5;15m [39m[38;5;204m=[39m[38;5;15m [39m[38;5;141m1[39m
+      10 | [38;5;15m[39m[38;5;15mlst[39m[38;5;15m [39m[38;5;204m=[39m[38;5;15m [39m[38;5;15m([39m
       11 | [38;5;15m        [39m[38;5;15m[[39m
       12 | [38;5;15m            [39m[38;5;15mx[39m[38;5;15m,[39m
 (...)
-      18 | [38;5;15m        [39m[38;5;197m+[39m[38;5;15m [39m[38;5;15m[[39m[38;5;15m][39m
+      18 | [38;5;15m        [39m[38;5;204m+[39m[38;5;15m [39m[38;5;15m[[39m[38;5;15m][39m
       19 | [38;5;15m[39m[38;5;15m)[39m
       20 | [38;5;15m[39m[38;5;81mtry[39m[38;5;15m:[39m
 -->   21 | [38;5;15m    [39m[38;5;81mreturn[39m[38;5;15m [39m[38;5;15;48;5;24mint[39;49m[38;5;15;48;5;24m([39;49m[38;5;15;48;5;24mstr[39;49m[38;5;15;48;5;24m([39;49m[38;5;15;48;5;24mlst[39;49m[38;5;15;48;5;24m)[39;49m[38;5;15;48;5;24m)[39;49m
@@ -19,7 +19,7 @@ Traceback (most recent call last):
       21 | [38;5;15m    [39m[38;5;81mreturn[39m[38;5;15m [39m[38;5;15mint[39m[38;5;15m([39m[38;5;15mstr[39m[38;5;15m([39m[38;5;15mlst[39m[38;5;15m)[39m[38;5;15m)[39m
       22 | [38;5;15m[39m[38;5;81mexcept[39m[38;5;15m:[39m
       23 | [38;5;15m    [39m[38;5;81mtry[39m[38;5;15m:[39m
--->   24 | [38;5;15m        [39m[38;5;81mreturn[39m[38;5;15m [39m[38;5;141;48;5;24m1[39;49m[38;5;15;48;5;24m [39;49m[38;5;197;48;5;24m/[39;49m[38;5;15;48;5;24m [39;49m[38;5;141;48;5;24m0[39;49m
+-->   24 | [38;5;15m        [39m[38;5;81mreturn[39m[38;5;15m [39m[38;5;141;48;5;24m1[39;49m[38;5;15;48;5;24m [39;49m[38;5;204;48;5;24m/[39;49m[38;5;15;48;5;24m [39;49m[38;5;141;48;5;24m0[39;49m
       25 | [38;5;15m    [39m[38;5;81mexcept[39m[38;5;15m [39m[38;5;148mException[39m[38;5;15m [39m[38;5;81mas[39m[38;5;15m [39m[38;5;15me[39m[38;5;15m:[39m
 ZeroDivisionError: division by zero
 
@@ -31,24 +31,24 @@ Traceback (most recent call last):
 -->   30 | [38;5;15m    [39m[38;5;15;48;5;24mexec[39;49m[38;5;15;48;5;24m([39;49m[38;5;186;48;5;24m"[39;49m[38;5;186;48;5;24mfoo()[39;49m[38;5;186;48;5;24m"[39;49m[38;5;15;48;5;24m)[39;49m
  File "<string>", line 1, in <module>
  File "formatter_example.py", line 8, in foo
-       6 | [38;5;81mdef[39m[38;5;15m [39m[38;5;148mfoo[39m[38;5;15m([39m[38;5;15mn[39m[38;5;197m=[39m[38;5;141m5[39m[38;5;15m)[39m[38;5;15m:[39m
-       7 | [38;5;15m    [39m[38;5;81mif[39m[38;5;15m [39m[38;5;15mn[39m[38;5;15m [39m[38;5;197m>[39m[38;5;15m [39m[38;5;141m0[39m[38;5;15m:[39m
--->    8 | [38;5;15m        [39m[38;5;81mreturn[39m[38;5;15m [39m[38;5;15;48;5;24mfoo[39;49m[38;5;15;48;5;24m([39;49m[38;5;15;48;5;24mn[39;49m[38;5;15;48;5;24m [39;49m[38;5;197;48;5;24m-[39;49m[38;5;15;48;5;24m [39;49m[38;5;141;48;5;24m1[39;49m[38;5;15;48;5;24m)[39;49m
-       9 | [38;5;15m    [39m[38;5;15mx[39m[38;5;15m [39m[38;5;197m=[39m[38;5;15m [39m[38;5;141m1[39m
+       6 | [38;5;81mdef[39m[38;5;15m [39m[38;5;148mfoo[39m[38;5;15m([39m[38;5;15mn[39m[38;5;204m=[39m[38;5;141m5[39m[38;5;15m)[39m[38;5;15m:[39m
+       7 | [38;5;15m    [39m[38;5;81mif[39m[38;5;15m [39m[38;5;15mn[39m[38;5;15m [39m[38;5;204m>[39m[38;5;15m [39m[38;5;141m0[39m[38;5;15m:[39m
+-->    8 | [38;5;15m        [39m[38;5;81mreturn[39m[38;5;15m [39m[38;5;15;48;5;24mfoo[39;49m[38;5;15;48;5;24m([39;49m[38;5;15;48;5;24mn[39;49m[38;5;15;48;5;24m [39;49m[38;5;204;48;5;24m-[39;49m[38;5;15;48;5;24m [39;49m[38;5;141;48;5;24m1[39;49m[38;5;15;48;5;24m)[39;49m
+       9 | [38;5;15m    [39m[38;5;15mx[39m[38;5;15m [39m[38;5;204m=[39m[38;5;15m [39m[38;5;141m1[39m
  File "formatter_example.py", line 8, in foo
-       6 | [38;5;81mdef[39m[38;5;15m [39m[38;5;148mfoo[39m[38;5;15m([39m[38;5;15mn[39m[38;5;197m=[39m[38;5;141m5[39m[38;5;15m)[39m[38;5;15m:[39m
-       7 | [38;5;15m    [39m[38;5;81mif[39m[38;5;15m [39m[38;5;15mn[39m[38;5;15m [39m[38;5;197m>[39m[38;5;15m [39m[38;5;141m0[39m[38;5;15m:[39m
--->    8 | [38;5;15m        [39m[38;5;81mreturn[39m[38;5;15m [39m[38;5;15;48;5;24mfoo[39;49m[38;5;15;48;5;24m([39;49m[38;5;15;48;5;24mn[39;49m[38;5;15;48;5;24m [39;49m[38;5;197;48;5;24m-[39;49m[38;5;15;48;5;24m [39;49m[38;5;141;48;5;24m1[39;49m[38;5;15;48;5;24m)[39;49m
-       9 | [38;5;15m    [39m[38;5;15mx[39m[38;5;15m [39m[38;5;197m=[39m[38;5;15m [39m[38;5;141m1[39m
+       6 | [38;5;81mdef[39m[38;5;15m [39m[38;5;148mfoo[39m[38;5;15m([39m[38;5;15mn[39m[38;5;204m=[39m[38;5;141m5[39m[38;5;15m)[39m[38;5;15m:[39m
+       7 | [38;5;15m    [39m[38;5;81mif[39m[38;5;15m [39m[38;5;15mn[39m[38;5;15m [39m[38;5;204m>[39m[38;5;15m [39m[38;5;141m0[39m[38;5;15m:[39m
+-->    8 | [38;5;15m        [39m[38;5;81mreturn[39m[38;5;15m [39m[38;5;15;48;5;24mfoo[39;49m[38;5;15;48;5;24m([39;49m[38;5;15;48;5;24mn[39;49m[38;5;15;48;5;24m [39;49m[38;5;204;48;5;24m-[39;49m[38;5;15;48;5;24m [39;49m[38;5;141;48;5;24m1[39;49m[38;5;15;48;5;24m)[39;49m
+       9 | [38;5;15m    [39m[38;5;15mx[39m[38;5;15m [39m[38;5;204m=[39m[38;5;15m [39m[38;5;141m1[39m
     [... skipping similar frames: foo at line 8 (2 times)]
  File "formatter_example.py", line 8, in foo
-       6 | [38;5;81mdef[39m[38;5;15m [39m[38;5;148mfoo[39m[38;5;15m([39m[38;5;15mn[39m[38;5;197m=[39m[38;5;141m5[39m[38;5;15m)[39m[38;5;15m:[39m
-       7 | [38;5;15m    [39m[38;5;81mif[39m[38;5;15m [39m[38;5;15mn[39m[38;5;15m [39m[38;5;197m>[39m[38;5;15m [39m[38;5;141m0[39m[38;5;15m:[39m
--->    8 | [38;5;15m        [39m[38;5;81mreturn[39m[38;5;15m [39m[38;5;15;48;5;24mfoo[39;49m[38;5;15;48;5;24m([39;49m[38;5;15;48;5;24mn[39;49m[38;5;15;48;5;24m [39;49m[38;5;197;48;5;24m-[39;49m[38;5;15;48;5;24m [39;49m[38;5;141;48;5;24m1[39;49m[38;5;15;48;5;24m)[39;49m
-       9 | [38;5;15m    [39m[38;5;15mx[39m[38;5;15m [39m[38;5;197m=[39m[38;5;15m [39m[38;5;141m1[39m
+       6 | [38;5;81mdef[39m[38;5;15m [39m[38;5;148mfoo[39m[38;5;15m([39m[38;5;15mn[39m[38;5;204m=[39m[38;5;141m5[39m[38;5;15m)[39m[38;5;15m:[39m
+       7 | [38;5;15m    [39m[38;5;81mif[39m[38;5;15m [39m[38;5;15mn[39m[38;5;15m [39m[38;5;204m>[39m[38;5;15m [39m[38;5;141m0[39m[38;5;15m:[39m
+-->    8 | [38;5;15m        [39m[38;5;81mreturn[39m[38;5;15m [39m[38;5;15;48;5;24mfoo[39;49m[38;5;15;48;5;24m([39;49m[38;5;15;48;5;24mn[39;49m[38;5;15;48;5;24m [39;49m[38;5;204;48;5;24m-[39;49m[38;5;15;48;5;24m [39;49m[38;5;141;48;5;24m1[39;49m[38;5;15;48;5;24m)[39;49m
+       9 | [38;5;15m    [39m[38;5;15mx[39m[38;5;15m [39m[38;5;204m=[39m[38;5;15m [39m[38;5;141m1[39m
  File "formatter_example.py", line 26, in foo
       23 | [38;5;15m[39m[38;5;81mtry[39m[38;5;15m:[39m
-      24 | [38;5;15m    [39m[38;5;81mreturn[39m[38;5;15m [39m[38;5;141m1[39m[38;5;15m [39m[38;5;197m/[39m[38;5;15m [39m[38;5;141m0[39m
+      24 | [38;5;15m    [39m[38;5;81mreturn[39m[38;5;15m [39m[38;5;141m1[39m[38;5;15m [39m[38;5;204m/[39m[38;5;15m [39m[38;5;141m0[39m
       25 | [38;5;15m[39m[38;5;81mexcept[39m[38;5;15m [39m[38;5;148mException[39m[38;5;15m [39m[38;5;81mas[39m[38;5;15m [39m[38;5;15me[39m[38;5;15m:[39m
--->   26 | [38;5;15m    [39m[38;5;81mraise[39m[38;5;15m [39m[38;5;148mTypeError[39m[38;5;15m [39m[38;5;197mfrom[39m[38;5;15m [39m[38;5;15me[39m
+-->   26 | [38;5;15m    [39m[38;5;81mraise[39m[38;5;15m [39m[38;5;148mTypeError[39m[38;5;15m [39m[38;5;204mfrom[39m[38;5;15m [39m[38;5;15me[39m
 TypeError
diff --git a/tests/golden_files/serialize.json b/tests/golden_files/serialize.json
index 94a190a..f084d18 100644
--- a/tests/golden_files/serialize.json
+++ b/tests/golden_files/serialize.json
@@ -582,13 +582,13 @@
                             "type": "line",
                             "is_current": false,
                             "lineno": 9,
-                            "text": "\u001b[38;5;15m\u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
+                            "text": "\u001b[38;5;15m\u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 10,
-                            "text": "\u001b[38;5;15m\u001b[39m\u001b[38;5;15mlst\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15m(\u001b[39m"
+                            "text": "\u001b[38;5;15m\u001b[39m\u001b[38;5;15mlst\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15m(\u001b[39m"
                         },
                         {
                             "type": "line",
@@ -609,7 +609,7 @@
                             "type": "line",
                             "is_current": false,
                             "lineno": 18,
-                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;197m+\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15m[\u001b[39m\u001b[38;5;15m]\u001b[39m"
+                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;204m+\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15m[\u001b[39m\u001b[38;5;15m]\u001b[39m"
                         },
                         {
                             "type": "line",
@@ -724,7 +724,7 @@
                             "type": "line",
                             "is_current": true,
                             "lineno": 24,
-                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;197;48;5;24m/\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m0\u001b[39;49m"
+                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;204;48;5;24m/\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m0\u001b[39;49m"
                         },
                         {
                             "type": "line",
@@ -832,25 +832,25 @@
                             "type": "line",
                             "is_current": false,
                             "lineno": 6,
-                            "text": "\u001b[38;5;81mdef\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mfoo\u001b[39m\u001b[38;5;15m(\u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;141m5\u001b[39m\u001b[38;5;15m)\u001b[39m\u001b[38;5;15m:\u001b[39m"
+                            "text": "\u001b[38;5;81mdef\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mfoo\u001b[39m\u001b[38;5;15m(\u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;141m5\u001b[39m\u001b[38;5;15m)\u001b[39m\u001b[38;5;15m:\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 7,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mif\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m>\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m\u001b[38;5;15m:\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mif\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m>\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m\u001b[38;5;15m:\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": true,
                             "lineno": 8,
-                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15;48;5;24mfoo\u001b[39;49m\u001b[38;5;15;48;5;24m(\u001b[39;49m\u001b[38;5;15;48;5;24mn\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;197;48;5;24m-\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m)\u001b[39;49m"
+                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15;48;5;24mfoo\u001b[39;49m\u001b[38;5;15;48;5;24m(\u001b[39;49m\u001b[38;5;15;48;5;24mn\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;204;48;5;24m-\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m)\u001b[39;49m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 9,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
                         }
                     ],
                     "variables": [
@@ -878,25 +878,25 @@
                             "type": "line",
                             "is_current": false,
                             "lineno": 6,
-                            "text": "\u001b[38;5;81mdef\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mfoo\u001b[39m\u001b[38;5;15m(\u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;141m5\u001b[39m\u001b[38;5;15m)\u001b[39m\u001b[38;5;15m:\u001b[39m"
+                            "text": "\u001b[38;5;81mdef\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mfoo\u001b[39m\u001b[38;5;15m(\u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;141m5\u001b[39m\u001b[38;5;15m)\u001b[39m\u001b[38;5;15m:\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 7,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mif\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m>\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m\u001b[38;5;15m:\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mif\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m>\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m\u001b[38;5;15m:\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": true,
                             "lineno": 8,
-                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15;48;5;24mfoo\u001b[39;49m\u001b[38;5;15;48;5;24m(\u001b[39;49m\u001b[38;5;15;48;5;24mn\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;197;48;5;24m-\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m)\u001b[39;49m"
+                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15;48;5;24mfoo\u001b[39;49m\u001b[38;5;15;48;5;24m(\u001b[39;49m\u001b[38;5;15;48;5;24mn\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;204;48;5;24m-\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m)\u001b[39;49m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 9,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
                         }
                     ],
                     "variables": [
@@ -934,25 +934,25 @@
                             "type": "line",
                             "is_current": false,
                             "lineno": 6,
-                            "text": "\u001b[38;5;81mdef\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mfoo\u001b[39m\u001b[38;5;15m(\u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;141m5\u001b[39m\u001b[38;5;15m)\u001b[39m\u001b[38;5;15m:\u001b[39m"
+                            "text": "\u001b[38;5;81mdef\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mfoo\u001b[39m\u001b[38;5;15m(\u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;141m5\u001b[39m\u001b[38;5;15m)\u001b[39m\u001b[38;5;15m:\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 7,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mif\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m>\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m\u001b[38;5;15m:\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mif\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m>\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m\u001b[38;5;15m:\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": true,
                             "lineno": 8,
-                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15;48;5;24mfoo\u001b[39;49m\u001b[38;5;15;48;5;24m(\u001b[39;49m\u001b[38;5;15;48;5;24mn\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;197;48;5;24m-\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m)\u001b[39;49m"
+                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15;48;5;24mfoo\u001b[39;49m\u001b[38;5;15;48;5;24m(\u001b[39;49m\u001b[38;5;15;48;5;24mn\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;204;48;5;24m-\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m)\u001b[39;49m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 9,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
                         }
                     ],
                     "variables": [
@@ -986,7 +986,7 @@
                             "type": "line",
                             "is_current": false,
                             "lineno": 24,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m/\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m/\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m"
                         },
                         {
                             "type": "line",
@@ -998,7 +998,7 @@
                             "type": "line",
                             "is_current": true,
                             "lineno": 26,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mraise\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mTypeError\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197mfrom\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15me\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mraise\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mTypeError\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204mfrom\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15me\u001b[39m"
                         }
                     ],
                     "variables": [
diff --git a/tests/test_formatter.py b/tests/test_formatter.py
index 31862f8..2ef9ecf 100644
--- a/tests/test_formatter.py
+++ b/tests/test_formatter.py
@@ -3,8 +3,9 @@ import re
 import sys
 from contextlib import contextmanager
 
-import pytest
 import pygments
+import pytest
+from asttokens.util import fstring_positions_work
 
 from stack_data import Formatter, FrameInfo, Options, BlankLines
 from tests.utils import compare_to_file
@@ -82,8 +83,7 @@ def test_example(capsys):
 
     if sys.version_info[:2] < (3, 8):
         f_string_suffix = 'old'
-    elif sys.version_info[:2] == (3, 8):
-        # lineno/col_offset in f-strings cannot be trusted in 3.8
+    elif not fstring_positions_work():
         f_string_suffix = '3.8'
     else:
         f_string_suffix = 'new'
diff --git a/tests/test_serializer.py b/tests/test_serializer.py
index 807872d..bc8acca 100644
--- a/tests/test_serializer.py
+++ b/tests/test_serializer.py
@@ -39,4 +39,4 @@ def test_example():
         )
 
 
-    compare_to_file_json(result, "serialize")
+    compare_to_file_json(result, "serialize", pygmented=True)
diff --git a/tests/utils.py b/tests/utils.py
index 7ba00e2..1500086 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,9 +1,19 @@
 import os
 
+import pygments
 from littleutils import string_to_file, file_to_string, json_to_file, file_to_json
 
 
+def parse_version(version: str):
+    return tuple(int(x) for x in version.split("."))
+
+
+old_pygments = parse_version(pygments.__version__) < (2, 16, 1)
+
+
 def compare_to_file(text, name):
+    if old_pygments and "pygment" in name:
+        return
     filename = os.path.join(
         os.path.dirname(__file__),
         'golden_files',
@@ -16,7 +26,9 @@ def compare_to_file(text, name):
         assert text == expected_output
 
 
-def compare_to_file_json(data, name):
+def compare_to_file_json(data, name, *, pygmented):
+    if old_pygments and pygmented:
+        return
     filename = os.path.join(
         os.path.dirname(__file__),
         'golden_files',