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: 8 additions & 16 deletions .github/scripts/test_upstream_convergence_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def manifest_entry(path: str, upstream_blob: str | None) -> dict[str, object]:
}


APP_SERVER_REGISTRY = "codex-rs/app-server/tests/suite/mod.rs"
V2_REGISTRY = "codex-rs/app-server/tests/suite/v2/mod.rs"

# Owned implementations and proofs that landed after the last guard regeneration.
Expand Down Expand Up @@ -421,26 +422,17 @@ def test_manifest_records_why_each_path_is_guarded(self) -> None:

self.assertEqual({"ownership_baseline", "current_tree"}, sources)

def test_owned_app_server_proofs_register_in_the_guarded_v2_registry(self) -> None:
"""The crate-level app-server registry carries no owned entry.
def test_owned_app_server_proofs_register_in_guarded_registries(self) -> None:
"""Both app-server registries now carry owned integration proofs."""

Every Every Code-owned app-server proof is a v2 suite, so reverting
`suite/v2/mod.rs` unregisters all of them while every proof file stays in
the tree. That is exactly the failure the crate-level registry cannot
catch, which is why its own `reverted_to_upstream` waiver is
`converged_with_upstream` rather than pending work.
"""

entry = guarded_entry(self, V2_REGISTRY)
crate_entry = guarded_entry(self, APP_SERVER_REGISTRY)
v2_entry = guarded_entry(self, V2_REGISTRY)
waivers = guard.load_waivers(guard.DEFAULT_WAIVERS)

self.assertEqual("intentionally_owned", entry["lane"])
self.assertEqual("intentionally_owned", crate_entry["lane"])
self.assertEqual("intentionally_owned", v2_entry["lane"])
self.assertNotIn(guard.waiver_key(APP_SERVER_REGISTRY, guard.REVERTED), waivers)
self.assertNotIn(guard.waiver_key(V2_REGISTRY, guard.REVERTED), waivers)
crate_registry = waivers[
guard.waiver_key("codex-rs/app-server/tests/suite/mod.rs", guard.REVERTED)
]
self.assertEqual("converged_with_upstream", crate_registry["disposition"])
self.assertIn(V2_REGISTRY, crate_registry["reason"])

def test_reverting_the_v2_registry_to_upstream_is_detected(self) -> None:
entry = guarded_entry(self, V2_REGISTRY)
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions codex-rs/app-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ codex-utils-pty = { workspace = true }
codex-backend-client = { workspace = true }
codex-file-search = { workspace = true }
codex-chatgpt = { workspace = true }
codex-keyring-store = { workspace = true }
codex-login = { workspace = true }
codex-image-generation-extension = { workspace = true }
codex-memories-extension = { workspace = true }
Expand Down Expand Up @@ -130,6 +131,7 @@ codex-code-bridge-service = { workspace = true }
codex-model-provider-info = { workspace = true }
codex-utils-cargo-bin = { workspace = true }
core_test_support = { workspace = true }
ctor = { workspace = true }
flate2 = { workspace = true }
hmac = { workspace = true }
opentelemetry = { workspace = true }
Expand Down
21 changes: 21 additions & 0 deletions codex-rs/app-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,27 @@ use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::registry::Registry;
use tracing_subscriber::util::SubscriberInitExt;

#[cfg(debug_assertions)]
use codex_keyring_store::TEST_KEYRING_DIR_ENV_VAR;

#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn install_test_keyring_store_from_env() -> anyhow::Result<()> {
let test_keyring_dir = std::env::var_os(TEST_KEYRING_DIR_ENV_VAR).ok_or_else(|| {
anyhow::anyhow!(
"{TEST_KEYRING_DIR_ENV_VAR} must be set when --use-test-keyring-store is used"
)
})?;
let test_keyring_dir = std::path::PathBuf::from(test_keyring_dir);
anyhow::ensure!(
codex_keyring_store::tests::install_persisted_default_test_keyring_store(
&test_keyring_dir
)?,
"test keyring store was already configured"
);
Ok(())
}

const SQLITE_RECOVERY_CONFIG_WARNING_SUMMARY: &str = "Codex rebuilt its local database.";

mod analytics_utils;
Expand Down
14 changes: 14 additions & 0 deletions codex-rs/app-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use codex_app_server::AppServerRuntimeOptions;
use codex_app_server::AppServerTransport;
use codex_app_server::AppServerWebsocketAuthArgs;
use codex_app_server::PluginStartupTasks;
#[cfg(debug_assertions)]
use codex_app_server::install_test_keyring_store_from_env;
use codex_app_server::run_main_with_transport_options;
use codex_arg0::Arg0DispatchPaths;
use codex_arg0::arg0_dispatch_or_else;
Expand Down Expand Up @@ -57,6 +59,12 @@ struct AppServerArgs {
#[arg(long = "disable-plugin-startup-tasks-for-tests", hide = true)]
disable_plugin_startup_tasks_for_tests: bool,

/// Hidden debug-only test hook used to redirect credential storage away
/// from the host keyring.
#[cfg(debug_assertions)]
#[arg(long = "use-test-keyring-store", hide = true)]
use_test_keyring_store: bool,

/// Enable remote control for this app-server process without changing persistence.
#[arg(long = "remote-control", hide = true)]
remote_control: bool,
Expand All @@ -74,6 +82,8 @@ fn main() -> anyhow::Result<()> {
strict_config,
#[cfg(debug_assertions)]
disable_plugin_startup_tasks_for_tests,
#[cfg(debug_assertions)]
use_test_keyring_store,
remote_control,
} = AppServerArgs::parse();
let loader_overrides = if disable_managed_config_from_debug_env() {
Expand All @@ -90,6 +100,10 @@ fn main() -> anyhow::Result<()> {
..Default::default()
};
#[cfg(debug_assertions)]
if use_test_keyring_store {
install_test_keyring_store_from_env()?;
}
#[cfg(debug_assertions)]
if disable_plugin_startup_tasks_for_tests {
runtime_options.plugin_startup_tasks = PluginStartupTasks::Skip;
}
Expand Down
36 changes: 36 additions & 0 deletions codex-rs/app-server/tests/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,40 @@

// Single integration test binary that aggregates all test modules.
// The submodules live in `tests/suite/`.
#[cfg(debug_assertions)]
use ctor::ctor;
#[cfg(debug_assertions)]
use ctor::dtor;
#[cfg(debug_assertions)]
use std::io::Write;

#[cfg(debug_assertions)]
#[ctor]
fn install_test_keyring_store() {
let install_result = codex_keyring_store::tests::install_persisted_default_test_keyring_store(
codex_keyring_store::tests::shared_test_keyring_root(),
);
if !matches!(install_result, Ok(true)) {
let mut stderr = std::io::stderr().lock();
let _ = writeln!(
stderr,
"failed to install persisted app-server test keyring store: {install_result:?}"
);
drop(stderr);
std::process::abort();
}
}

#[cfg(debug_assertions)]
#[dtor]
fn remove_test_keyring_store() {
if let Err(error) = codex_keyring_store::tests::remove_shared_test_keyring_root() {
let mut stderr = std::io::stderr().lock();
let _ = writeln!(
stderr,
"failed to remove app-server test keyring directory: {error}"
);
}
}

mod suite;
1 change: 1 addition & 0 deletions codex-rs/app-server/tests/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ codex-config = { workspace = true }
codex-core = { workspace = true }
codex-exec-server = { workspace = true }
codex-features = { workspace = true }
codex-keyring-store = { workspace = true }
codex-login = { workspace = true }
codex-models-manager = { workspace = true }
codex-protocol = { workspace = true }
Expand Down
32 changes: 26 additions & 6 deletions codex-rs/app-server/tests/common/json_logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use serde_json::Value;
use serde_json::json;
use tokio::sync::Notify;

#[cfg(debug_assertions)]
use crate::configure_test_keyring_for_std_command;

#[derive(Clone, Default)]
pub(crate) struct JsonLogCapture {
lines: Arc<Mutex<Vec<String>>>,
Expand Down Expand Up @@ -75,26 +78,43 @@ impl JsonLogCapture {
}
}

#[derive(Debug, Clone, Copy)]
pub enum AppServerJsonInvocation {
Standalone,
CodexCli,
}

pub fn app_server_json_shutdown_event(
binary: &str,
args: &[&str],
invocation: AppServerJsonInvocation,
codex_home: &Path,
) -> Result<Value> {
std::fs::write(
codex_home.join("config.toml"),
"[features]\nplugins = false\n",
)?;
let output = Command::new(codex_utils_cargo_bin::cargo_bin(binary)?)
let binary = match invocation {
AppServerJsonInvocation::Standalone => "codex-app-server",
AppServerJsonInvocation::CodexCli => "codex",
};
let mut command = Command::new(codex_utils_cargo_bin::cargo_bin(binary)?);
command
.stdin(Stdio::null())
.env("CODEX_LAB_HOME", codex_home)
.env(
"CODEX_APP_SERVER_MANAGED_CONFIG_PATH",
codex_home.join("managed_config.toml"),
)
.env("LOG_FORMAT", "json")
.env("RUST_LOG", "codex_app_server=info")
.args(args)
.output()?;
.env("RUST_LOG", "codex_app_server=info");
if matches!(invocation, AppServerJsonInvocation::CodexCli) {
command.arg("app-server");
}
#[cfg(debug_assertions)]
configure_test_keyring_for_std_command(
&mut command,
&codex_home.join("app-server-test-keyring"),
);
let output = command.output()?;

let stderr = String::from_utf8(output.stderr)?;
anyhow::ensure!(output.status.success(), "app-server failed: {stderr}");
Expand Down
9 changes: 9 additions & 0 deletions codex-rs/app-server/tests/common/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ pub use auth_fixtures::ChatGptIdTokenClaims;
pub use auth_fixtures::encode_id_token;
pub use auth_fixtures::write_chatgpt_auth;
use codex_app_server_protocol::JSONRPCResponse;
#[cfg(debug_assertions)]
pub use codex_keyring_store::TEST_KEYRING_DIR_ENV_VAR;
pub use config::MockResponsesConfig;
pub use config::write_mock_responses_config_toml;
pub use config::write_mock_responses_config_toml_with_chatgpt_base_url;
Expand All @@ -30,6 +32,7 @@ pub use core_test_support::test_absolute_path;
pub use core_test_support::test_path_buf_with_windows;
pub use core_test_support::test_tmp_path;
pub use core_test_support::test_tmp_path_buf;
pub use json_logging::AppServerJsonInvocation;
pub use json_logging::app_server_json_shutdown_event;
pub use mock_model_server::create_mock_responses_server_repeating_assistant;
pub use mock_model_server::create_mock_responses_server_sequence;
Expand All @@ -54,6 +57,12 @@ pub use test_app_server::DEFAULT_CLIENT_NAME;
pub use test_app_server::DISABLE_PLUGIN_STARTUP_TASKS_ARG;
pub use test_app_server::TestAppServer;
pub use test_app_server::TestAppServerBuilder;
#[cfg(debug_assertions)]
pub use test_app_server::USE_TEST_KEYRING_STORE_ARG;
#[cfg(debug_assertions)]
pub use test_app_server::configure_test_keyring_for_std_command;
#[cfg(debug_assertions)]
pub use test_app_server::configure_test_keyring_for_tokio_command;

pub fn to_response<T: DeserializeOwned>(response: JSONRPCResponse) -> anyhow::Result<T> {
let value = serde_json::to_value(response.result)?;
Expand Down
22 changes: 22 additions & 0 deletions codex-rs/app-server/tests/common/test_app_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ use codex_exec_server::CODEX_EXEC_SERVER_NOISE_CHATGPT_ACCOUNT_ID_ENV_VAR;
use codex_exec_server::CODEX_EXEC_SERVER_NOISE_ENVIRONMENT_ID_ENV_VAR;
use codex_exec_server::CODEX_EXEC_SERVER_NOISE_REGISTRY_URL_ENV_VAR;
use codex_exec_server::CODEX_EXEC_SERVER_URL_ENV_VAR;
#[cfg(debug_assertions)]
use codex_keyring_store::TEST_KEYRING_DIR_ENV_VAR;
#[cfg(debug_assertions)]
use codex_keyring_store::tests::shared_test_keyring_root;
use codex_login::default_client::CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR;
use core_test_support::is_remote_test_environment;
use core_test_support::test_codex::TestEnv;
Expand Down Expand Up @@ -163,6 +167,8 @@ pub struct TestAppServer {

pub const DEFAULT_CLIENT_NAME: &str = "codex-app-server-tests";
pub const DISABLE_PLUGIN_STARTUP_TASKS_ARG: &str = "--disable-plugin-startup-tasks-for-tests";
#[cfg(debug_assertions)]
pub const USE_TEST_KEYRING_STORE_ARG: &str = "--use-test-keyring-store";
const DISABLE_MANAGED_CONFIG_ENV_VAR: &str = "CODEX_APP_SERVER_DISABLE_MANAGED_CONFIG";
const CODE_MODE_HOST_PATH_ENV_VAR: &str = "CODEX_CODE_MODE_HOST_PATH";
#[cfg(windows)]
Expand Down Expand Up @@ -247,6 +253,8 @@ impl TestAppServer {
);
cmd.env_remove(CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR);
cmd.args(args);
#[cfg(debug_assertions)]
configure_test_keyring_for_tokio_command(&mut cmd, shared_test_keyring_root());

for (k, v) in env_overrides {
match v {
Expand Down Expand Up @@ -1793,6 +1801,20 @@ impl TestAppServer {
}
}

#[cfg(debug_assertions)]
pub fn configure_test_keyring_for_std_command(command: &mut std::process::Command, root: &Path) {
command
.arg(USE_TEST_KEYRING_STORE_ARG)
.env(TEST_KEYRING_DIR_ENV_VAR, root);
}

#[cfg(debug_assertions)]
pub fn configure_test_keyring_for_tokio_command(command: &mut Command, root: &Path) {
command
.arg(USE_TEST_KEYRING_STORE_ARG)
.env(TEST_KEYRING_DIR_ENV_VAR, root);
}

/// Builder for TestAppServer.
pub struct TestAppServerBuilder {
codex_home: Option<PathBuf>,
Expand Down
Loading
Loading