Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 11 additions & 13 deletions tests/test_codegen_audit_fixes.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,26 +254,24 @@ def test_array_slice_string_elements():
# C18 — bare time variables use the exchange timezone path
# ---------------------------------------------------------------------------

@pytest.mark.parametrize("var,field", [
("hour", "tm_buf.tm_hour"),
("minute", "tm_buf.tm_min"),
("second", "tm_buf.tm_sec"),
("dayofmonth", "tm_buf.tm_mday"),
("dayofweek", "tm_buf.tm_wday + 1"),
("month", "tm_buf.tm_mon + 1"),
("year", "tm_buf.tm_year + 1900"),
@pytest.mark.parametrize("var", [
"hour", "minute", "second", "dayofmonth", "dayofweek", "month", "year",
])
def test_bare_time_vars_use_exchange_timezone(var, field):
def test_bare_time_vars_use_exchange_timezone(var):
# Bare time vars route through the engine helper pine_<var>() threading the
# exchange TZ (syminfo_.timezone); the tm-field extraction and Pine offsets
# live inside the helper (session_time.cpp, KI-35), not the generated code.
cpp = _gen(f"x = {var}\nplot(close)\n")
assert f"_bar_{var}()" not in cpp
assert "syminfo_.timezone" in cpp
assert field in cpp
assert f"pine_{var}(current_bar_.timestamp, syminfo_.timezone)" in cpp


def test_bare_and_function_form_share_emission():
cpp = _gen("a = hour\nb = hour(time)\nplot(close)\n")
# Both forms route through the same tz-aware lambda body.
assert cpp.count("tm_buf.tm_hour") == 2
# Both the bare and function forms route through the same engine helper
# pine_hour() (the bare form passes the timestamp directly, the function
# form casts its time arg to int64_t).
assert cpp.count("pine_hour(") == 2
assert "_bar_hour()" not in cpp


Expand Down
45 changes: 20 additions & 25 deletions tests/test_codegen_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,12 +252,13 @@ def test_hour_two_arg_passes_tz():
'plot(h)\n'
)
cpp = _generate(src)
# The tz literal is preserved (auditable) as the pine_hour() call argument;
# the tz-aware decomposition now lives in the engine helper (session_time.cpp)
# rather than an inline setenv+tzset+localtime_r lambda — the per-call tzset
# churn caused a macOS notifyd storm (KI-35).
assert "America/New_York" in cpp
assert "normalize_timezone_for_posix" in cpp
# Two-arg form must use localtime_r (with the TZ env mutation) rather
# than just gmtime_r — that is the whole point of the tz argument.
assert "localtime_r" in cpp
assert "setenv" in cpp
assert ('pine_hour((int64_t)(current_bar_.timestamp), '
'std::string("America/New_York"))') in cpp


def test_hour_one_arg_uses_syminfo_timezone():
Expand All @@ -275,18 +276,14 @@ def test_hour_one_arg_uses_syminfo_timezone():
'h = hour(time)\n'
)
cpp = _generate(src)
# The 1-arg form must reference syminfo_.timezone (exchange TZ) per
# TV docs. The default ``SymInfo::timezone`` of "UTC" keeps the
# cheap gmtime_r path active for crypto.
assert "syminfo_.timezone" in cpp
assert "normalize_timezone_for_posix" in cpp
# The chart-display TZ slot must NOT leak into the bar-time lambda;
# The 1-arg form must thread syminfo_.timezone (exchange TZ) into the engine
# helper pine_hour() per TV docs. The UTC fast path (tzset-free gmtime_r) now
# lives inside pine_hour (session_time.cpp), not the generated code (KI-35).
assert ("pine_hour((int64_t)(current_bar_.timestamp), "
"syminfo_.timezone)") in cpp
# The chart-display TZ slot must NOT leak into the bar-time call;
# if it ever does, this test catches the regression.
assert "chart_timezone_" not in cpp
# Both branches of the lambda are emitted; cheap gmtime_r still serves
# the UTC default.
assert "gmtime_r" in cpp
assert "localtime_r" in cpp


def test_dayofweek_two_arg_passes_tz():
Expand All @@ -300,25 +297,23 @@ def test_dayofweek_two_arg_passes_tz():
)
cpp = _generate(src)
assert "Asia/Tokyo" in cpp
assert "localtime_r" in cpp
assert ('pine_dayofweek((int64_t)(current_bar_.timestamp), '
'std::string("Asia/Tokyo"))') in cpp


def test_hour_two_arg_utc_literal_short_circuits():
"""Pine ``hour(time, "UTC")`` should emit a tz-aware lambda but at
runtime take the gmtime_r branch (the inline ``if (_tz == "UTC")``
check). The emitted source still references the literal so the tz
intent is auditable."""
"""Pine ``hour(time, "UTC")`` routes through the engine helper pine_hour();
the UTC fast path (tzset-free gmtime_r) lives inside the helper
(session_time.cpp, KI-35). The emitted source still references the "UTC"
literal so the tz intent is auditable."""
src = (
'//@version=6\nstrategy("T")\n'
'h = hour(time, "UTC")\n'
)
cpp = _generate(src)
assert '"UTC"' in cpp
# Both branches are emitted in the lambda; both gmtime_r and localtime_r
# appear in the source even though only the gmtime_r branch runs at
# runtime for this literal.
assert "gmtime_r" in cpp
assert "localtime_r" in cpp
assert ('pine_hour((int64_t)(current_bar_.timestamp), '
'std::string("UTC"))') in cpp


def test_time_in_request_security_uses_bar_timestamp():
Expand Down
Loading