Skip to content

Commit ba4986c

Browse files
test: add test coverage for LOG_LEVEL
Signed-off-by: Scott Schreckengaust <345885+scottschreckengaust@users.noreply.github.com>
1 parent 54495c5 commit ba4986c

1 file changed

Lines changed: 220 additions & 0 deletions

File tree

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
"""Tests for checkov/logging_init.py LOG_LEVEL handling.
2+
3+
Since logging_init.py executes at module import time, we test the core
4+
logic (basicConfig + setLevel with env-driven LOG_LEVEL) in isolation
5+
to verify that invalid values don't crash the process.
6+
"""
7+
import logging
8+
import os
9+
import subprocess
10+
import sys
11+
import unittest
12+
13+
from checkov.logging_init import FALLBACK_LOG_LEVEL
14+
15+
16+
def _configure_logging(log_level_env: str | None = None) -> int | str:
17+
"""Reproduce the initialization logic from checkov/logging_init.py.
18+
19+
Returns the effective LOG_LEVEL (str on success, int on fallback).
20+
"""
21+
raw = (log_level_env if log_level_env is not None
22+
else logging.getLevelName(FALLBACK_LOG_LEVEL)).upper()
23+
try:
24+
logging.basicConfig(level=raw, force=True)
25+
return raw
26+
except (ValueError, TypeError):
27+
logging.basicConfig(level=FALLBACK_LOG_LEVEL, force=True)
28+
return FALLBACK_LOG_LEVEL
29+
30+
31+
# ---------------------------------------------------------------------------
32+
# Valid Python log levels – these must all succeed
33+
# ---------------------------------------------------------------------------
34+
VALID_PYTHON_LEVELS = [
35+
"DEBUG",
36+
"INFO",
37+
"WARNING",
38+
"ERROR",
39+
"CRITICAL",
40+
"NOTSET",
41+
]
42+
43+
# ---------------------------------------------------------------------------
44+
# Valid Python aliases – accepted by logging but not the canonical names
45+
# ---------------------------------------------------------------------------
46+
VALID_PYTHON_ALIASES = [
47+
"FATAL", # alias for CRITICAL
48+
"WARN", # alias for WARNING
49+
]
50+
51+
# ---------------------------------------------------------------------------
52+
# Case-insensitive variants – .upper() should normalise these
53+
# ---------------------------------------------------------------------------
54+
CASE_VARIANTS = [
55+
("debug", "DEBUG"),
56+
("info", "INFO"),
57+
("Warning", "WARNING"),
58+
("warning", "WARNING"),
59+
("WaRnInG", "WARNING"),
60+
("error", "ERROR"),
61+
("Error", "ERROR"),
62+
("critical", "CRITICAL"),
63+
("CrItIcAl", "CRITICAL"),
64+
("notset", "NOTSET"),
65+
("fatal", "FATAL"),
66+
("Warn", "WARN"),
67+
]
68+
69+
# ---------------------------------------------------------------------------
70+
# Invalid / non-Python level strings – must fall back to WARNING
71+
# ---------------------------------------------------------------------------
72+
INVALID_LEVELS = [
73+
# Common levels from other ecosystems (Java, Rust, syslog, etc.)
74+
"TRACE",
75+
"VERBOSE",
76+
"SEVERE",
77+
"FINE",
78+
"FINER",
79+
"FINEST",
80+
"OFF",
81+
"ALL",
82+
"NOTICE",
83+
"ALERT",
84+
"EMERG",
85+
"EMERGENCY",
86+
# Misspellings
87+
"DBUG",
88+
"DEUBG",
89+
"DUBUG",
90+
"INFOO",
91+
"WARINING",
92+
"WARNNING",
93+
"WARING",
94+
"EROR",
95+
"ERRROR",
96+
"CRTICAL",
97+
"CRITCAL",
98+
# Garbage
99+
"",
100+
" ",
101+
"123",
102+
"NONE",
103+
"NULL",
104+
"TRUE",
105+
"FALSE",
106+
"YES",
107+
"NO",
108+
]
109+
110+
ALL_VALID_LEVELS = VALID_PYTHON_LEVELS + VALID_PYTHON_ALIASES
111+
112+
113+
class TestLoggingInitValidLevels(unittest.TestCase):
114+
"""Verify every valid Python log level is accepted without error."""
115+
116+
def test_valid_levels(self) -> None:
117+
for level in VALID_PYTHON_LEVELS:
118+
with self.subTest(level=level):
119+
result = _configure_logging(level)
120+
self.assertEqual(result, level)
121+
122+
def test_valid_aliases(self) -> None:
123+
for level in VALID_PYTHON_ALIASES:
124+
with self.subTest(level=level):
125+
result = _configure_logging(level)
126+
self.assertEqual(result, level)
127+
128+
129+
class TestLoggingInitCaseInsensitivity(unittest.TestCase):
130+
"""LOG_LEVEL should be case-insensitive thanks to .upper()."""
131+
132+
def test_case_variants(self) -> None:
133+
for raw, expected in CASE_VARIANTS:
134+
with self.subTest(raw=raw):
135+
result = _configure_logging(raw)
136+
self.assertEqual(result, expected)
137+
138+
139+
class TestLoggingInitInvalidLevels(unittest.TestCase):
140+
"""Invalid LOG_LEVEL values must not crash; they should fall back."""
141+
142+
def test_invalid_levels_do_not_crash(self) -> None:
143+
for level in INVALID_LEVELS:
144+
with self.subTest(level=level):
145+
result = _configure_logging(level)
146+
self.assertEqual(result, FALLBACK_LOG_LEVEL,
147+
f"Expected fallback for invalid level {level!r}")
148+
149+
def test_none_env_defaults_to_warning(self) -> None:
150+
result = _configure_logging(None)
151+
self.assertEqual(result, logging.getLevelName(FALLBACK_LOG_LEVEL))
152+
153+
154+
class TestLoggingInitSetLevel(unittest.TestCase):
155+
"""Verify setLevel behaviour with valid and invalid level strings."""
156+
157+
def test_setLevel_with_valid_levels(self) -> None:
158+
handler = logging.StreamHandler()
159+
for level in ALL_VALID_LEVELS:
160+
with self.subTest(level=level):
161+
handler.setLevel(level) # should not raise
162+
163+
def test_setLevel_rejects_invalid_levels(self) -> None:
164+
"""setLevel raises ValueError on invalid strings, confirming that
165+
the fallback in logging_init.py must reassign LOG_LEVEL so that
166+
downstream setLevel() calls don't crash.
167+
"""
168+
handler = logging.StreamHandler()
169+
for level in INVALID_LEVELS:
170+
if not level.strip():
171+
continue # empty/whitespace handled differently
172+
with self.subTest(level=level):
173+
with self.assertRaises(ValueError,
174+
msg=f"setLevel({level!r}) should raise ValueError"):
175+
handler.setLevel(level.upper())
176+
177+
178+
class TestLoggingInitModuleImport(unittest.TestCase):
179+
"""End-to-end: verify the actual module import doesn't crash.
180+
181+
Uses subprocess so the module-level code runs fresh in a clean
182+
Python process. Skipped if checkov dependencies aren't installed.
183+
"""
184+
185+
def _import_logging_init(self, log_level: str) -> subprocess.CompletedProcess:
186+
env = os.environ.copy()
187+
env["LOG_LEVEL"] = log_level
188+
return subprocess.run(
189+
[sys.executable, "-c", "import checkov.logging_init"],
190+
env=env,
191+
capture_output=True,
192+
text=True,
193+
timeout=30,
194+
)
195+
196+
def test_valid_levels_import_succeeds(self) -> None:
197+
for level in ALL_VALID_LEVELS:
198+
with self.subTest(level=level):
199+
result = self._import_logging_init(level)
200+
self.assertEqual(result.returncode, 0,
201+
f"Import crashed with LOG_LEVEL={level!r}:\n{result.stderr}")
202+
203+
def test_invalid_levels_import_does_not_crash(self) -> None:
204+
"""Invalid LOG_LEVEL values must not crash the import."""
205+
for level in ["DBUG", "TRACE", "VERBOSE", "INFOO", "WARINING"]:
206+
with self.subTest(level=level):
207+
result = self._import_logging_init(level)
208+
self.assertEqual(result.returncode, 0,
209+
f"Import crashed with LOG_LEVEL={level!r}:\n{result.stderr}")
210+
211+
def test_case_insensitive_import(self) -> None:
212+
for level in ["Warning", "debug", "error", "fatal", "Warn"]:
213+
with self.subTest(level=level):
214+
result = self._import_logging_init(level)
215+
self.assertEqual(result.returncode, 0,
216+
f"Import crashed with LOG_LEVEL={level!r}:\n{result.stderr}")
217+
218+
219+
if __name__ == "__main__":
220+
unittest.main()

0 commit comments

Comments
 (0)