1+ # Silence streamlit's "missing ScriptRunContext" / "No runtime found" /
2+ # "Session state does not function" warnings, which fire whenever streamlit-
3+ # decorated code runs outside an active script run (our CLI, scheduler, server,
4+ # and any import that touches @st.cache_data). Must run before the first
5+ # streamlit-using import, so it sits at the top of the module.
6+ #
7+ # We replace ``set_log_level`` itself, after seeding it to "error". Streamlit's
8+ # own ``_update_logger`` callback fires on config parse and would otherwise
9+ # downgrade us back to "info"; the cap floors any later call at ERROR.
10+ def _silence_streamlit_logs () -> None :
11+ import logging as _logging
12+
13+ try :
14+ from streamlit import logger as _st_logger
15+ except ImportError :
16+ return
17+
18+ _original = _st_logger .set_log_level
19+ _original ("error" )
20+
21+ def _capped (level ):
22+ if isinstance (level , str ):
23+ try :
24+ level_num = getattr (_logging , level .upper ())
25+ except AttributeError :
26+ _original (level )
27+ return
28+ else :
29+ level_num = level
30+ _original (max (level_num , _logging .ERROR ))
31+
32+ _st_logger .set_log_level = _capped
33+
34+
35+ _silence_streamlit_logs ()
36+
37+
138import base64
239import importlib
340import logging
441import os
42+ import pathlib
543import platform
644import secrets
745import signal
77115VERSION_DATA = version_service .get_version ()
78116CHILDREN_POLL_INTERVAL = 10
79117
118+
119+ def _forward_signal_to_child (child : subprocess .Popen , signum : int ) -> None :
120+ # On POSIX, forward the signal verbatim. On Windows, subprocess.send_signal
121+ # rejects everything except SIGTERM / CTRL_C_EVENT / CTRL_BREAK_EVENT, so
122+ # fall back to terminate() — equivalent to TerminateProcess().
123+ if sys .platform == "win32" :
124+ child .terminate ()
125+ else :
126+ child .send_signal (signum )
127+
80128@dataclass
81129class Configuration :
82130 verbose : bool = field (default = False )
@@ -94,6 +142,7 @@ def invoke(self, ctx: Context):
94142 raise
95143 except Exception :
96144 LOG .exception ("There was an unexpected error" )
145+ sys .exit (1 )
97146
98147 def format_epilog (self , _ctx : Context , formatter : click .HelpFormatter ) -> None :
99148 # Schema revision is a DB round-trip; defer until `--help` is actually
@@ -551,9 +600,24 @@ def generate_secret(length: int = 12) -> str:
551600 "TG_TARGET_DB_TRUST_SERVER_CERTIFICATE=yes" ,
552601 "TG_EXPORT_TO_OBSERVABILITY_VERIFY_SSL=no" ,
553602 ]
603+
604+ # Persist caller-supplied runtime overrides (ports, TLS) so they apply to
605+ # subsequent `testgen run-app` invocations.
606+ persisted_env_vars = ("TG_UI_PORT" , "TG_API_PORT" , "TESTGEN_LOG_FILE_PATH" , "SSL_CERT_FILE" , "SSL_KEY_FILE" )
607+ persisted_lines = [f"{ name } ={ os .environ [name ]} " for name in persisted_env_vars if os .environ .get (name )]
608+ if persisted_lines :
609+ config_lines .extend (["" , "# Runtime overrides from installer" , * persisted_lines ])
610+
554611 config_path .write_text ("\n " .join (config_lines ) + "\n " )
555612 click .echo (f"Config written to { config_path } " )
556613
614+ # `getenv` resolves env vars before config.env, so a pre-existing
615+ # TESTGEN_USERNAME / TESTGEN_PASSWORD in the shell would override the
616+ # CLI-supplied values and get seeded into the DB. Force the CLI args
617+ # to win for the rest of this process.
618+ os .environ ["TESTGEN_USERNAME" ] = username
619+ os .environ ["TESTGEN_PASSWORD" ] = password
620+
557621 # Reload settings — the module was already evaluated at import time
558622 # before the config file existed. Reloading re-reads the new file
559623 # and re-evaluates all module-level variables.
@@ -564,6 +628,14 @@ def generate_secret(length: int = 12) -> str:
564628 from testgen .ui .scripts .patch_streamlit import patch as patch_streamlit
565629 patch_streamlit (dev = True )
566630
631+ # Seed Streamlit's first-run credentials file so `run-app` doesn't block
632+ # on the interactive email prompt. We don't care about the value — just
633+ # that the file exists so Streamlit skips the prompt.
634+ streamlit_creds = pathlib .Path .home () / ".streamlit" / "credentials.toml"
635+ if not streamlit_creds .exists ():
636+ streamlit_creds .parent .mkdir (parents = True , exist_ok = True )
637+ streamlit_creds .write_text ('[general]\n email = ""\n ' )
638+
567639 # Start embedded PostgreSQL (standalone mode is now active via config)
568640 start_standalone_postgres ()
569641
@@ -860,14 +932,18 @@ def init_ui():
860932 child_env = {** os .environ , "TG_JOB_SOURCE" : "UI" , STANDALONE_URI_ENV_VAR : server_uri }
861933
862934 process = subprocess .Popen (
863- [ # noqa: S607
935+ [
936+ sys .executable ,
937+ "-m" ,
864938 "streamlit" ,
865939 "run" ,
866940 app_file ,
867941 "--browser.gatherUsageStats=false" ,
942+ f"--logger.level={ 'debug' if settings .IS_DEBUG else 'error' } " ,
868943 "--client.showErrorDetails=none" ,
869944 "--client.toolbarMode=minimal" ,
870945 "--server.enableStaticServing=true" ,
946+ f"--server.port={ settings .UI_PORT } " ,
871947 f"--server.sslCertFile={ settings .SSL_CERT_FILE } " if use_ssl else "" ,
872948 f"--server.sslKeyFile={ settings .SSL_KEY_FILE } " if use_ssl else "" ,
873949 "--" ,
@@ -877,7 +953,7 @@ def init_ui():
877953 )
878954 def term_ui (signum , _ ):
879955 LOG .info (f"Sending termination signal { signum } to Testgen UI" )
880- process . send_signal ( signum )
956+ _forward_signal_to_child ( process , signum )
881957 signal .signal (signal .SIGINT , term_ui )
882958 signal .signal (signal .SIGTERM , term_ui )
883959 status_code = process .wait ()
@@ -905,13 +981,13 @@ def run_app(module):
905981
906982 case "all" :
907983 children = [
908- subprocess .Popen ([sys .executable , sys . argv [ 0 ] , "run-app" , m ], start_new_session = True )
984+ subprocess .Popen ([sys .executable , "-m" , "testgen" , "run-app" , m ], start_new_session = True )
909985 for m in APP_MODULES
910986 ]
911987
912988 def term_children (signum , _ ):
913989 for child in children :
914- child . send_signal ( signum )
990+ _forward_signal_to_child ( child , signum )
915991
916992 signal .signal (signal .SIGINT , term_children )
917993 signal .signal (signal .SIGTERM , term_children )
0 commit comments