Repository: edgedb/edgedb-cli
Pull request #1294 (2024-04-18 16:18:26)

Organize and document tests
Document different types of tests we have, what they do and their requirements.

Move "common util" files into "tests/common/" dir.

Rename a few things.


Patch:
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 0000000..fc306fb
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,6 @@
+# add this snippet to .git/config:
+# [blame]
+#   ignoreRevsFile = .git-blame-ignore-revs
+
+# rustfmt
+5e744e6153923b0f9f979c72229d8d2636603100
diff --git a/Cargo.toml b/Cargo.toml
index 790b0d6..3e9294e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,7 +20,7 @@ path = "src/main.rs"
 dev_mode = []
 github_action_install = []
 github_nightly = []
-portable_tests = ["shared-client-tests/portable_tests"]
+portable_tests = []
 docker_test_wrapper = []
 
 [workspace.dependencies]
diff --git a/README.md b/README.md
index 35fe1f4..b8e29f2 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,40 @@ cargo run -- --admin -d tutorial
 cargo test
 ```
 
+Tests
+=====
+
+There are a few categories of tests in this repo:
+
+- unit tests within `src/`
+  - run with: `cargo test --bins`,
+  - no additional requirements,
+
+- `tests/func/`
+  - invokes the cli binary,
+  - run with: `cargo test --test=func`,
+  - requires `edgedb-server` binary in PATH,
+
+- `tests/shared-client-tests/`
+  - generates tests from [shared-client-testcases](https://github.com/edgedb/shared-client-testcases/),
+  - invokes the cli binary,
+  - run with: `cargo test --package=shared-client-tests`,
+  - will write into `/home/edgedb`,
+
+- `tests/portable_*.rs/`
+  - tests installation of the portable EdgeDB server,
+  - will download large packages,
+  - run with: `cargo test --features=portable_tests --test=portable_X`,
+  - assumes you don't have any portables installed before running it,
+
+- `tests/docker_test_wrapper.rs`
+  - runs other tests in a docker container,
+  - run with: `cargo test --features=docker_test_wrapper --test=docker_test_wrapper`,
+  - requires Docker,
+  - requires that binaries compiled on host machine are runnable in "ubuntu:mantic",
+
+- Github Actions & Nightly tests
+
 
 License
 =======
diff --git a/src/branch/drop.rs b/src/branch/drop.rs
index f35bd6a..30ef1b2 100644
--- a/src/branch/drop.rs
+++ b/src/branch/drop.rs
@@ -11,7 +11,7 @@ pub async fn main(
     connection: &mut Connection,
 ) -> anyhow::Result<()> {
     if let Some(current_branch) = &context.branch {
-        if current_branch == &options.branch {
+        if current_branch == &options.target_branch {
             anyhow::bail!(
                 "Dropping the currently active branch is not supported, please switch to a \
                 different branch to drop this one with `edgedb branch switch <branch>`"
@@ -22,7 +22,7 @@ pub async fn main(
     if !options.non_interactive {
         let q = question::Confirm::new_dangerous(format!(
             "Do you really want to drop the branch {:?}?",
-            options.branch
+            options.target_branch
         ));
         if !connection.ping_while(q.async_ask()).await? {
             print::error("Canceled by user.");
@@ -32,7 +32,7 @@ pub async fn main(
 
     let mut statement = format!(
         "drop branch {}",
-        edgeql_parser::helpers::quote_name(&options.branch)
+        edgeql_parser::helpers::quote_name(&options.target_branch)
     );
 
     if options.force {
diff --git a/src/branch/main.rs b/src/branch/main.rs
index 80c7f83..442b325 100644
--- a/src/branch/main.rs
+++ b/src/branch/main.rs
@@ -1,7 +1,7 @@
 use crate::branch::context::Context;
 use crate::branch::option::{BranchCommand, Command};
 use crate::branch::{create, current, drop, list, merge, rebase, rename, switch, wipe};
-use crate::commands::Options;
+use crate::commands::{CommandResult, Options};
 use crate::connect::{Connection, Connector};
 
 use edgedb_tokio::get_project_dir;
@@ -10,7 +10,9 @@ use edgedb_tokio::get_project_dir;
 pub async fn branch_main(options: &Options, cmd: &BranchCommand) -> anyhow::Result<()> {
     let context = create_context().await?;
 
-    run_branch_command(&cmd.subcommand, options, &context, None).await
+    run_branch_command(&cmd.subcommand, options, &context, None).await?;
+
+    Ok(())
 }
 
 pub async fn run_branch_command(
@@ -18,21 +20,23 @@ pub async fn run_branch_command(
     options: &Options,
     context: &Context,
     connection: Option<&mut Connection>,
-) -> anyhow::Result<()> {
+) -> anyhow::Result<Option<CommandResult>> {
     let mut connector: Connector = options.conn_params.clone();
 
     match &cmd {
-        Command::Switch(switch) => switch::main(switch, context, &mut connector).await,
+        Command::Switch(switch) => return switch::main(switch, context, &mut connector).await,
         Command::Wipe(wipe) => wipe::main(wipe, context, &mut connector).await,
         Command::Current(current) => current::main(current, context).await,
         command => match connection {
-            Some(conn) => run_branch_command1(command, conn, context, options).await,
+            Some(conn) => return run_branch_command1(command, conn, context, options).await,
             None => {
                 let mut conn = connector.connect().await?;
-                run_branch_command1(command, &mut conn, context, options).await
+                return run_branch_command1(command, &mut conn, context, options).await;
             }
         },
-    }
+    }?;
+
+    Ok(None)
 }
 
 async fn run_branch_command1(
@@ -40,18 +44,20 @@ async fn run_branch_command1(
     connection: &mut Connection,
     context: &Context,
     options: &Options,
-) -> anyhow::Result<()> {
+) -> anyhow::Result<Option<CommandResult>> {
     verify_server_can_use_branches(connection).await?;
 
     match command {
         Command::Create(create) => create::main(create, context, connection).await,
         Command::Drop(drop) => drop::main(drop, context, connection).await,
         Command::List(list) => list::main(list, context, connection).await,
-        Command::Rename(rename) => rename::main(rename, context, connection, options).await,
+        Command::Rename(rename) => return rename::main(rename, context, connection, options).await,
         Command::Rebase(rebase) => rebase::main(rebase, context, connection, options).await,
         Command::Merge(merge) => merge::main(merge, context, connection, options).await,
         unhandled => anyhow::bail!("unimplemented branch command '{:?}'", unhandled),
-    }
+    }?;
+
+    Ok(None)
 }
 
 pub async fn create_context() -> anyhow::Result<Context> {
diff --git a/src/branch/option.rs b/src/branch/option.rs
index fa948db..4baf95f 100644
--- a/src/branch/option.rs
+++ b/src/branch/option.rs
@@ -13,14 +13,14 @@ pub struct BranchCommand {
 #[derive(clap::Subcommand, Clone, Debug)]
 pub enum Command {
     Create(Create),
-    Drop(Drop),
-    Wipe(Wipe),
     Switch(Switch),
-    Rename(Rename),
     List(List),
+    Current(Current),
     Rebase(Rebase),
     Merge(Merge),
-    Current(Current),
+    Rename(Rename),
+    Drop(Drop),
+    Wipe(Wipe),
 }
 
 impl From<&BranchingCmd> for Command {
@@ -29,11 +29,14 @@ impl From<&BranchingCmd> for Command {
             BranchingCmd::Create(args) => Command::Create(args.clone()),
             BranchingCmd::Drop(args) => Command::Drop(args.clone()),
             BranchingCmd::Wipe(args) => Command::Wipe(args.clone()),
+            BranchingCmd::List(args) => Command::List(args.clone()),
+            BranchingCmd::Switch(args) => Command::Switch(args.clone()),
+            BranchingCmd::Rename(args) => Command::Rename(args.clone()),
         }
     }
 }
 
-/// Creates a new branch and switches to it.
+/// Creates a new branch.
 #[derive(clap::Args, Debug, Clone)]
 pub struct Create {
     /// The name of the branch to create.
@@ -56,7 +59,7 @@ pub struct Create {
 #[derive(clap::Args, Debug, Clone)]
 pub struct Drop {
     /// The branch to drop.
-    pub branch: String,
+    pub target_branch: String,
 
     /// Drop the branch without asking for confirmation.
     #[arg(long)]
@@ -71,7 +74,7 @@ pub struct Drop {
 #[derive(clap::Args, Debug, Clone)]
 pub struct Wipe {
     /// The branch to wipe.
-    pub branch: String,
+    pub target_branch: String,
 
     /// Wipe without asking for confirmation.
     #[arg(long)]
@@ -82,7 +85,7 @@ pub struct Wipe {
 #[derive(clap::Args, Debug, Clone)]
 pub struct Switch {
     /// The branch to switch to.
-    pub branch: String,
+    pub target_branch: String,
 
     /// Create the branch if it doesn't exist.
     #[arg(short = 'c', long)]
@@ -119,9 +122,8 @@ pub struct Rename {
 #[derive(clap::Args, Debug, Clone)]
 pub struct List {}
 
-/// Creates a new branch that is based on the target branch, but also
-/// contains any new migrations on the current branch.
-/// Warning: data stored in current branch will be deleted.
+/// Creates a new branch that is based on the target branch, but also contains any new migrations
+/// on the current branch. Warning: data stored in current branch will be deleted.
 #[derive(clap::Args, Debug, Clone)]
 pub struct Rebase {
     /// The branch to rebase the current branch to.
diff --git a/src/branch/rename.rs b/src/branch/rename.rs
index b71ab30..baf3128 100644
--- a/src/branch/rename.rs
+++ b/src/branch/rename.rs
@@ -1,7 +1,7 @@
 use crate::branch::connections::get_connection_to_modify;
 use crate::branch::context::Context;
 use crate::branch::option::Rename;
-use crate::commands::Options;
+use crate::commands::{CommandResult, Options};
 use crate::connect::Connection;
 use crate::print;
 
@@ -10,7 +10,7 @@ pub async fn main(
     context: &Context,
     connection: &mut Connection,
     cli_opts: &Options,
-) -> anyhow::Result<()> {
+) -> anyhow::Result<Option<CommandResult>> {
     if Some(&options.old_name) == context.branch.as_ref()
         || connection.database() == options.old_name
     {
@@ -28,7 +28,13 @@ pub async fn main(
         options.old_name, options.new_name
     );
 
-    Ok(())
+    if connection.database() == options.old_name {
+        return Ok(Some(CommandResult {
+            new_branch: Some(options.new_name.clone()),
+        }));
+    }
+
+    Ok(None)
 }
 
 async fn rename(connection: &mut Connection, options: &Rename) -> anyhow::Result<()> {
diff --git a/src/branch/switch.rs b/src/branch/switch.rs
index 0caaf71..ba4f6f4 100644
--- a/src/branch/switch.rs
+++ b/src/branch/switch.rs
@@ -3,21 +3,22 @@ use crate::branch::context::Context;
 use crate::branch::create::create_branch;
 use crate::branch::main::verify_server_can_use_branches;
 use crate::branch::option::Switch;
+use crate::commands::CommandResult;
 use crate::connect::Connector;
 
 pub async fn main(
     options: &Switch,
     context: &Context,
     connector: &mut Connector,
-) -> anyhow::Result<()> {
+) -> anyhow::Result<Option<CommandResult>> {
     if context.branch.is_none() {
         anyhow::bail!("Cannot switch branches: No project found");
     }
 
     let current_branch = context.branch.as_ref().unwrap();
 
-    if current_branch == &options.branch {
-        anyhow::bail!("Already on '{}'", options.branch);
+    if current_branch == &options.target_branch {
+        anyhow::bail!("Already on '{}'", options.target_branch);
     }
 
     if let Some(mut connection) = connect_if_branch_exists(connector).await? {
@@ -31,24 +32,24 @@ pub async fn main(
             )
             .await?;
 
-        if !branches.contains(&options.branch) {
+        if !branches.contains(&options.target_branch) {
             if options.create {
-                eprintln!("Creating '{}'...", &options.branch);
+                eprintln!("Creating '{}'...", &options.target_branch);
                 create_branch(
                     &mut connection,
-                    &options.branch,
+                    &options.target_branch,
                     options.from.as_ref().unwrap_or(current_branch),
                     options.empty,
                     options.copy_data,
                 )
                 .await?;
             } else {
-                anyhow::bail!("Branch '{}' doesn't exists", options.branch)
+                anyhow::bail!("Branch '{}' doesn't exists", options.target_branch)
             }
         }
     } else {
         // try to connect to the target branch
-        let target_branch_connector = connector.branch(&options.branch)?;
+        let target_branch_connector = connector.branch(&options.target_branch)?;
         match connect_if_branch_exists(target_branch_connector).await? {
             Some(mut connection) => {
                 verify_server_can_use_branches(&mut connection).await?;
@@ -59,10 +60,12 @@ pub async fn main(
 
     eprintln!(
         "Switching from '{}' to '{}'",
-        current_branch, options.branch
+        current_branch, options.target_branch
     );
 
-    context.update_branch(&options.branch).await?;
+    context.update_branch(&options.target_branch).await?;
 
-    Ok(())
+    Ok(Some(CommandResult {
+        new_branch: Some(options.target_branch.clone()),
+    }))
 }
diff --git a/src/branch/wipe.rs b/src/branch/wipe.rs
index 9ae8f9b..f796de2 100644
--- a/src/branch/wipe.rs
+++ b/src/branch/wipe.rs
@@ -11,10 +11,10 @@ pub async fn main(
     _context: &Context,
     connector: &mut Connector,
 ) -> anyhow::Result<()> {
-    let connection = connect_if_branch_exists(connector.branch(&options.branch)?).await?;
+    let connection = connect_if_branch_exists(connector.branch(&options.target_branch)?).await?;
 
     if connection.is_none() {
-        anyhow::bail!("Branch '{}' doesn't exist", &options.branch)
+        anyhow::bail!("Branch '{}' doesn't exist", &options.target_branch)
     }
 
     let mut connection = connection.unwrap();
@@ -23,7 +23,7 @@ pub async fn main(
         let q = question::Confirm::new_dangerous(format!(
             "Do you really want to wipe \
                     the contents of the branch {:?}?",
-            options.branch
+            options.target_branch
         ));
         if !connection.ping_while(q.async_ask()).await? {
             print::error("Canceled by user.");
diff --git a/src/commands/backslash.rs b/src/commands/backslash.rs
index d2bfe19..54b696a 100644
--- a/src/commands/backslash.rs
+++ b/src/commands/backslash.rs
@@ -338,6 +338,7 @@ impl CommandCache {
         aliases.insert("?", &["help"]);
         aliases.insert("h", &["help"]);
         aliases.insert("branch", &["branching"]);
+        aliases.insert("b", &["branching"]);
         let mut setting_cmd = None;
         let commands: BTreeMap<_, _> = clap
             .get_subcommands_mut()
@@ -582,7 +583,14 @@ pub async fn execute(
         Common(ref cmd) => {
             prompt.soft_reconnect().await?;
             let cli = prompt.connection.as_mut().expect("connection established");
-            execute::common(cli, cmd, &options).await?;
+            let result = execute::common(cli, cmd, &options).await?;
+
+            if let Some(result) = result {
+                if let Some(branch) = result.new_branch {
+                    prompt.try_connect(&branch).await?;
+                }
+            }
+
             Ok(Skip)
         }
         Set(SetCommand { setting: None }) => {
diff --git a/src/commands/branching.rs b/src/commands/branching.rs
index 99a98f0..1d087e5 100644
--- a/src/commands/branching.rs
+++ b/src/commands/branching.rs
@@ -1,14 +1,13 @@
 use crate::branch;
 use crate::commands::parser::BranchingCmd;
-use crate::commands::Options;
+use crate::commands::{CommandResult, Options};
 use crate::connect::Connection;
 
 pub async fn main(
     connection: &mut Connection,
     cmd: &BranchingCmd,
     options: &Options,
-) -> anyhow::Result<()> {
+) -> anyhow::Result<Option<CommandResult>> {
     let context = branch::main::create_context().await?;
-
     branch::main::run_branch_command(&cmd.into(), options, &context, Some(connection)).await
 }
diff --git a/src/commands/command_result.rs b/src/commands/command_result.rs
new file mode 100644
index 0000000..338c1b4
--- /dev/null
+++ b/src/commands/command_result.rs
@@ -0,0 +1,3 @@
+pub struct CommandResult {
+    pub new_branch: Option<String>,
+}
diff --git a/src/commands/execute.rs b/src/commands/execute.rs
index f0dbf3d..d0af571 100644
--- a/src/commands/execute.rs
+++ b/src/commands/execute.rs
@@ -3,7 +3,7 @@ use edgedb_tokio::server_params::PostgresAddress;
 
 use crate::analyze;
 use crate::commands::parser::{Common, DatabaseCmd, DescribeCmd, ListCmd};
-use crate::commands::{self, branching, Options};
+use crate::commands::{self, branching, CommandResult, Options};
 use crate::migrations;
 use crate::migrations::options::MigrationCmd;
 use crate::print;
@@ -12,7 +12,7 @@ pub async fn common(
     cli: &mut Connection,
     cmd: &Common,
     options: &Options,
-) -> Result<(), anyhow::Error> {
+) -> Result<Option<CommandResult>, anyhow::Error> {
     use Common::*;
     match cmd {
         List(c) => match &c.subcommand {
@@ -104,7 +104,7 @@ pub async fn common(
                 commands::database::wipe(cli, w, options).await?;
             }
         },
-        Branching(branching) => branching::main(cli, &branching.subcommand, options).await?,
+        Branching(branching) => return branching::main(cli, &branching.subcommand, options).await,
         Migrate(params) => {
             migrations::migrate(cli, options, params).await?;
         }
@@ -135,5 +135,5 @@ pub async fn common(
             }
         },
     }
-    Ok(())
+    Ok(None)
 }
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index b7c9e1f..1efeb94 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -1,6 +1,7 @@
 pub mod backslash;
 mod branching;
 pub mod cli;
+mod command_result;
 mod configure;
 mod database;
 mod describe;
@@ -27,6 +28,7 @@ mod psql;
 mod restore;
 mod ui;
 
+pub use self::command_result::CommandResult;
 pub use self::configure::configure;
 pub use self::describe::describe;
 pub use self::describe_schema::describe_schema;
diff --git a/src/commands/parser.rs b/src/commands/parser.rs
index 29d6b47..36f3e99 100644
--- a/src/commands/parser.rs
+++ b/src/commands/parser.rs
@@ -122,7 +122,7 @@ pub enum ListCmd {
 }
 
 #[derive(clap::Args, Clone, Debug)]
-#[command(version = "help_expand")]
+#[command(version = "help_expand", hide = true)]
 #[command(disable_version_flag = true)]
 pub struct Branching {
     #[command(flatten)]
@@ -142,6 +142,12 @@ pub enum BranchingCmd {
     /// preserving the branch itself (its cfg::DatabaseConfig)
     /// and existing migration scripts
     Wipe(crate::branch::option::Wipe),
+    /// List all branches.
+    List(crate::branch::option::List),
+    /// Switches the current branch to a different one.
+    Switch(crate::branch::option::Switch),
+    /// Renames a branch.
+    Rename(crate::branch::option::Rename),
 }
 
 #[derive(clap::Args, Clone, Debug)]
diff --git a/src/migrations/context.rs b/src/migrations/context.rs
index a9cee1c..98c9ba3 100644
--- a/src/migrations/context.rs
+++ b/src/migrations/context.rs
@@ -32,7 +32,11 @@ impl Context {
             edgedb_version = Some(config.edgedb.server_version);
             config.project.schema_dir
         } else {
-            "./dbschema".into()
+            let default_dir: PathBuf = "./dbschema".into();
+            if !default_dir.exists() {
+                anyhow::bail!("`dbschema` directory doesn't exist. Either create one or provide path via --schema-dir.");
+            }
+            default_dir
         };
 
         Ok(Context {
diff --git a/src/portable/linux.rs b/src/portable/linux.rs
index 405851a..4820bfa 100644
--- a/src/portable/linux.rs
+++ b/src/portable/linux.rs
@@ -218,6 +218,10 @@ pub fn server_cmd(
     pro.env_default("EDGEDB_SERVER_LOG_LEVEL", "warn");
     pro.env_default("EDGEDB_SERVER_HTTP_ENDPOINT_SECURITY", "optional");
     pro.env_default("EDGEDB_SERVER_INSTANCE_NAME", &inst.name);
+    pro.env_default(
+        "EDGEDB_SERVER_CONFIG_cfg::auto_rebuild_query_cache",
+        "false",
+    );
     pro.arg("--data-dir").arg(data_dir);
     pro.arg("--runstate-dir").arg(runstate_dir(&inst.name)?);
     pro.arg("--port").arg(inst.port.to_string());
diff --git a/src/portable/macos.rs b/src/portable/macos.rs
index 856b9ea..4a0b583 100644
--- a/src/portable/macos.rs
+++ b/src/portable/macos.rs
@@ -300,6 +300,10 @@ pub fn server_cmd(
     pro.env_default("EDGEDB_SERVER_LOG_LEVEL", "warn");
     pro.env_default("EDGEDB_SERVER_HTTP_ENDPOINT_SECURITY", "optional");
     pro.env_default("EDGEDB_SERVER_INSTANCE_NAME", &inst.name);
+    pro.env_default(
+        "EDGEDB_SERVER_CONFIG_cfg::auto_rebuild_query_cache",
+        "false",
+    );
     pro.arg("--data-dir").arg(data_dir);
     pro.arg("--runstate-dir").arg(runstate_dir);
     pro.arg("--port").arg(inst.port.to_string());
diff --git a/src/portable/project.rs b/src/portable/project.rs
index 83c7e8d..3298da7 100644
--- a/src/portable/project.rs
+++ b/src/portable/project.rs
@@ -38,7 +38,7 @@ use crate::portable::platform::optional_docker_check;
 use crate::portable::repository::{self, Channel, PackageInfo, Query};
 use crate::portable::upgrade;
 use crate::portable::ver;
-use crate::portable::ver::Specific;
+use crate::portable::ver::{Specific};
 use crate::portable::windows;
 use crate::print::{self, echo, Highlight};
 
@@ -693,7 +693,7 @@ pub fn init_existing(
             ]);
 
             if !schema_files {
-                write_schema_default(&schema_dir, &ver_query)?;
+                write_schema_default(&schema_dir, &Query::from_version(&ver)?)?;
             }
             do_cloud_init(
                 name.to_owned(),
@@ -756,7 +756,7 @@ pub fn init_existing(
             table::settings(rows.as_slice());
 
             if !schema_files {
-                write_schema_default(&schema_dir, &ver_query)?;
+                write_schema_default(&schema_dir, &Query::from_version(&specific_version)?)?;
             }
 
             do_init(
@@ -1023,7 +1023,7 @@ pub fn init_new(
             ]);
             write_config(&config_path, &ver_query)?;
             if !schema_files {
-                write_schema_default(&schema_dir_path, &ver_query)?;
+                write_schema_default(&schema_dir_path, &Query::from_version(&version)?)?;
             }
 
             do_cloud_init(
@@ -1081,7 +1081,7 @@ pub fn init_new(
 
             write_config(&config_path, &ver_query)?;
             if !schema_files {
-                write_schema_default(&schema_dir_path, &ver_query)?;
+                write_schema_default(&schema_dir_path, &Query::from_version(&specific_version)?)?;
             }
 
             do_init(
@@ -1465,6 +1465,7 @@ fn write_schema_default(dir: &Path, version: &Query) -> anyhow::Result<()> {
     fs::remove_file(&tmp).ok();
     fs::write(&tmp, DEFAULT_ESDL)?;
     fs::rename(&tmp, &default)?;
+
     if version.is_nonrecursive_access_policies_needed() {
         let futures = dir.join("futures.esdl");
         let tmp = tmp_file_path(&futures);
diff --git a/src/portable/repository.rs b/src/portable/repository.rs
index 60ef83b..b809fdf 100644
--- a/src/portable/repository.rs
+++ b/src/portable/repository.rs
@@ -530,7 +530,14 @@ impl Query {
     pub fn from_version(ver: &ver::Specific) -> anyhow::Result<Query> {
         use crate::portable::repository::ver::{FilterMinor, MinorVersion};
         match ver.minor {
-            MinorVersion::Dev(_) => Ok(Query::nightly()),
+            MinorVersion::Dev(v) => Ok(Query {
+                channel: Channel::Nightly,
+                version: Some(ver::Filter {
+                    major: ver.major,
+                    minor: Some(FilterMinor::Minor(v)),
+                    exact: false,
+                }),
+            }),
             MinorVersion::Alpha(v) if ver.major == 1 => Ok(Query {
                 channel: Channel::Stable,
                 version: Some(ver::Filter {
@@ -618,7 +625,7 @@ impl Query {
                 (2, _) => true,
                 _ => false,
             })
-            .unwrap_or(true)
+            .unwrap_or(false)
     }
     pub fn cli_channel(&self) -> Option<Channel> {
         // Only one argument in CLI is allowed
diff --git a/vagga.yaml b/vagga.yaml
index ae0e339..ee88b6e 100644
--- a/vagga.yaml
+++ b/vagga.yaml
@@ -73,7 +73,7 @@ commands:
     - cargo
     - test
     - --features=docker_test_wrapper,portable_tests
-    - --test=docker_portable_wrapper
+    - --test=docker_test_wrapper
     environ:
       DOCKER_HOST: unix:///var/docker.sock
     volumes:
@@ -105,8 +105,7 @@ commands:
     run:
     - cargo
     - test
-    - --test=portable_shared
-    - --features=portable_tests
+    - --test=shared_client_tests
     environ:
       RUST_TEST_THREADS: 1
     volumes:


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 9795311..04fc2ce 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -83,7 +83,7 @@ jobs:
           server-version: ${{ matrix.edgedb-version }}
 
       - run: |
-          cargo test --target=x86_64-unknown-linux-musl --all
+          cargo test --target=x86_64-unknown-linux-musl
 
   portable-install-tests:
     runs-on: ${{ matrix.os }}
@@ -104,10 +104,10 @@ jobs:
           toolchain: "stable"
 
       - run: |
-          cargo build --all --tests --features docker_test_wrapper,portable_tests
+          cargo build --tests --features docker_test_wrapper,portable_tests
 
       - run: |
-          cargo test --all --test=docker_portable_wrapper \
+          cargo test --test=docker_test_wrapper \
             --features docker_test_wrapper,portable_tests
 
   portable-tests-macos:
@@ -115,7 +115,7 @@ jobs:
     timeout-minutes: 45
     strategy:
       matrix:
-        test: [portable_smoke, portable_project, portable_project_dir, portable_shared]
+        test: [portable_smoke, portable_project, portable_project_dir, shared_client_tests]
       fail-fast: false
     steps:
       - uses: actions/checkout@v4
@@ -129,7 +129,7 @@ jobs:
           toolchain: "stable"
 
       - run: |
-          cargo build --tests --features portable_tests
+          cargo build --workspace --test=${{ matrix.test }} --bin edgedb --features portable_tests
 
       - run: |
           sudo cp target/debug/edgedb /usr/local/bin/
@@ -137,7 +137,7 @@ jobs:
           sudo mv home_edgedb /Users/edgedb
 
       - run: |
-          cargo test --all --test=${{ matrix.test }} --features portable_tests
+          cargo test --workspace --test=${{ matrix.test }} --features portable_tests
 
   portable-tests-windows:
     needs: musl-test
@@ -145,7 +145,7 @@ jobs:
     timeout-minutes: 45
     strategy:
       matrix:
-        test: [portable_smoke, portable_project, portable_project_dir, portable_shared]
+        test: [portable_smoke, portable_project, portable_project_dir, shared_client_tests]
       fail-fast: false
     env:
       _EDGEDB_WSL_DISTRO: Debian
@@ -162,7 +162,7 @@ jobs:
           toolchain: "stable"
 
       - run: |
-          cargo build --tests --features portable_tests
+          cargo build --workspace --test=${{ matrix.test }} --features portable_tests
 
       - run: |
           cargo run -- cli install -y --no-wait-for-exit-prompt
@@ -183,7 +183,7 @@ jobs:
             distribution: Debian
 
       - run: |
-          cargo test --all --test=${{ matrix.test }} --features portable_tests
+          cargo test --workspace --test=${{ matrix.test }} --features portable_tests
 
   test-bin-installable:
     runs-on: ${{ matrix.os }}
diff --git a/tests/.gitignore b/tests/.gitignore
deleted file mode 100644
index fbae3a2..0000000
--- a/tests/.gitignore
+++ /dev/null
@@ -1,15 +0,0 @@
-/migrations/db1/initial/migrations/00002-*.edgeql
-/migrations/db1/initial/migrations/00003-*.edgeql
-/migrations/db1/modified1/migrations/00002-*.edgeql
-/migrations/db1/modified2/migrations/00002-*.edgeql
-/migrations/db1/modified3/migrations/00002-*.edgeql
-/migrations/db2/initial/migrations/00001-*.edgeql
-/migrations/db2/modified1/migrations/00002-*.edgeql
-/migrations/db3/migrations/00002-*.edgeql
-/migrations/db4/modified1/00001-*.edgeql
-/migrations/db4/created1/00002-*.edgeql
-/migrations/db1/project/priv/dbschema/migrations/00002-*.edgeql
-/migrations/db1/project/priv/dbschema/migrations/00003-*.edgeql
-/migrations/db1/squash
-/migrations/db4/created1/migrations/00002-*.edgeql
-/migrations/db4/modified1/migrations/00001-*.edgeql
diff --git a/tests/certs.rs b/tests/common/certs.rs
similarity index 100%
rename from tests/certs.rs
rename to tests/common/certs.rs
diff --git a/tests/docker.rs b/tests/common/docker.rs
similarity index 100%
rename from tests/docker.rs
rename to tests/common/docker.rs
diff --git a/tests/measure.rs b/tests/common/measure.rs
similarity index 100%
rename from tests/measure.rs
rename to tests/common/measure.rs
diff --git a/tests/util.rs b/tests/common/util.rs
similarity index 100%
rename from tests/util.rs
rename to tests/common/util.rs
diff --git a/tests/docker_portable_wrapper.rs b/tests/docker_test_wrapper.rs
similarity index 96%
rename from tests/docker_portable_wrapper.rs
rename to tests/docker_test_wrapper.rs
index b6b14e0..6d86a09 100644
--- a/tests/docker_portable_wrapper.rs
+++ b/tests/docker_test_wrapper.rs
@@ -13,9 +13,11 @@ use assert_cmd::Command;
 use once_cell::sync::Lazy;
 use test_case::test_case;
 
+#[path = "common/docker.rs"]
 mod docker;
-mod util;
 
+#[path = "common/util.rs"]
+mod util;
 use util::*;
 
 #[derive(serde::Deserialize)]
@@ -33,7 +35,7 @@ struct Target {
 static TEST_EXECUTABLES: Lazy<HashMap<String, PathBuf>> = Lazy::new(|| {
     let tests = std::process::Command::new("cargo")
         .arg("build")
-        .arg("--all")
+        .arg("--workspace")
         .arg("--tests")
         .arg("--features=docker_test_wrapper,portable_tests")
         .arg("--message-format=json")
@@ -42,7 +44,7 @@ static TEST_EXECUTABLES: Lazy<HashMap<String, PathBuf>> = Lazy::new(|| {
     let mut executables: HashMap<String, PathBuf> = HashMap::new();
     for line in tests.stdout.split(|&c| c == b'\n') {
         let art = match serde_json::from_slice::<Artifact>(line) {
-            Ok(art) if art.target.name.starts_with("portable") && art.target.test => art,
+            Ok(art) if art.target.test => art,
             Ok(_) | Err(_) => continue,
         };
         executables.insert(art.target.name.clone(), art.executable.into());
@@ -109,7 +111,7 @@ fn dockerfile() -> String {
 #[test_case("portable_smoke")]
 #[test_case("portable_project")]
 #[test_case("portable_project_dir")]
-#[test_case("portable_shared")]
+#[test_case("shared_client_tests")]
 fn run_test(name: &'static str) {
     let file_name = TEST_EXECUTABLES
         .get(name)
diff --git a/tests/func/main.rs b/tests/func/main.rs
index bb3c564..b28fbce 100644
--- a/tests/func/main.rs
+++ b/tests/func/main.rs
@@ -35,7 +35,7 @@ mod non_interactive;
 mod interactive;
 
 mod help;
-#[path = "../util.rs"]
+#[path = "../common/util.rs"]
 mod util;
 
 pub struct Config {
diff --git a/tests/github-actions/main.rs b/tests/github-actions/main.rs
index 0802fa9..826cf0c 100644
--- a/tests/github-actions/main.rs
+++ b/tests/github-actions/main.rs
@@ -1,10 +1,9 @@
-#[cfg(feature = "github_action_install")]
-#[path = "../docker.rs"]
+#![cfg(feature = "github_action_install")]
+
+#[path = "../common/docker.rs"]
 mod docker;
 
-#[cfg(feature = "github_action_install")]
-#[path = "../certs.rs"]
+#[path = "../common/certs.rs"]
 mod certs;
 
-#[cfg(feature = "github_action_install")]
 mod install;
diff --git a/tests/github-nightly/main.rs b/tests/github-nightly/main.rs
index b55396b..59ff9f5 100644
--- a/tests/github-nightly/main.rs
+++ b/tests/github-nightly/main.rs
@@ -1,19 +1,13 @@
-#[cfg(feature = "github_nightly")]
-#[path = "../docker.rs"]
+#![cfg(feature = "github_nightly")]
+
+#[path = "../common/docker.rs"]
 mod docker;
 
-#[cfg(feature = "github_nightly")]
-#[path = "../measure.rs"]
+#[path = "../common/measure.rs"]
 mod measure;
 
-#[cfg(feature = "github_nightly")]
 mod common;
-
-#[cfg(feature = "github_nightly")]
 mod compat;
-#[cfg(feature = "github_nightly")]
 mod install;
-#[cfg(feature = "github_nightly")]
 mod project;
-#[cfg(feature = "github_nightly")]
 mod upgrade;
diff --git a/tests/migrations/.gitignore b/tests/migrations/.gitignore
new file mode 100644
index 0000000..6faa20f
--- /dev/null
+++ b/tests/migrations/.gitignore
@@ -0,0 +1,15 @@
+/db1/initial/migrations/00002-*.edgeql
+/db1/initial/migrations/00003-*.edgeql
+/db1/modified1/migrations/00002-*.edgeql
+/db1/modified2/migrations/00002-*.edgeql
+/db1/modified3/migrations/00002-*.edgeql
+/db2/initial/migrations/00001-*.edgeql
+/db2/modified1/migrations/00002-*.edgeql
+/db3/migrations/00002-*.edgeql
+/db4/modified1/00001-*.edgeql
+/db4/created1/00002-*.edgeql
+/db1/project/priv/dbschema/migrations/00002-*.edgeql
+/db1/project/priv/dbschema/migrations/00003-*.edgeql
+/db1/squash
+/db4/created1/migrations/00002-*.edgeql
+/db4/modified1/migrations/00001-*.edgeql
diff --git a/tests/portable_project.rs b/tests/portable_project.rs
index 001aa64..aafdbd5 100644
--- a/tests/portable_project.rs
+++ b/tests/portable_project.rs
@@ -3,6 +3,7 @@
 use assert_cmd::Command;
 use predicates::prelude::*;
 
+#[path = "common/util.rs"]
 mod util;
 use util::*;
 
diff --git a/tests/portable_project_dir.rs b/tests/portable_project_dir.rs
index c225e9e..b079bfb 100644
--- a/tests/portable_project_dir.rs
+++ b/tests/portable_project_dir.rs
@@ -1,12 +1,12 @@
-#![cfg_attr(not(feature = "portable_tests"), allow(dead_code, unused_imports))]
+#![cfg(feature = "portable_tests")]
 
 use assert_cmd::Command;
 use predicates::prelude::*;
 
+#[path = "common/util.rs"]
 mod util;
 use util::*;
 
-#[cfg(feature = "portable_tests")]
 #[test]
 fn project_link_and_init_from_non_project_dir() {
     Command::new("edgedb")
diff --git a/tests/portable_smoke.rs b/tests/portable_smoke.rs
index 0a73a3c..66e5aa7 100644
--- a/tests/portable_smoke.rs
+++ b/tests/portable_smoke.rs
@@ -1,12 +1,12 @@
-#![cfg_attr(not(feature = "portable_tests"), allow(dead_code, unused_imports))]
+#![cfg(feature = "portable_tests")]
 
 use assert_cmd::Command;
 use predicates::prelude::*;
 
+#[path = "common/util.rs"]
 mod util;
 use util::*;
 
-#[cfg(feature = "portable_tests")]
 #[test]
 fn install() {
     Command::new("edgedb")
diff --git a/tests/shared-client-tests/Cargo.toml b/tests/shared-client-tests/Cargo.toml
index 4b3e4b6..26b41d0 100644
--- a/tests/shared-client-tests/Cargo.toml
+++ b/tests/shared-client-tests/Cargo.toml
@@ -5,9 +5,6 @@ version = "0.1.0"
 authors = ["EdgeDB Inc. <hello@edgedb.com>"]
 edition = "2018"
 
-[features]
-portable_tests = []
-
 [build-dependencies]
 serde_json = "1.0"
 
diff --git a/tests/shared-client-tests/tests/portable_shared.rs b/tests/shared-client-tests/tests/shared_client_tests.rs
similarity index 99%
rename from tests/shared-client-tests/tests/portable_shared.rs
rename to tests/shared-client-tests/tests/shared_client_tests.rs
index e0702d8..592bdc1 100644
--- a/tests/shared-client-tests/tests/portable_shared.rs
+++ b/tests/shared-client-tests/tests/shared_client_tests.rs
@@ -1,5 +1,3 @@
-#![cfg(feature = "portable_tests")]
-
 use assert_cmd::Command;
 use edgedb_protocol::model::Duration;
 use predicates::reflection::PredicateReflection;