Skip to content

Commit

Permalink
Merge pull request #121 from matthewmturner/feat/app-tests
Browse files Browse the repository at this point in the history
Start setting up tui tests
  • Loading branch information
alamb authored Sep 9, 2024
2 parents 40b0901 + f6e299b commit b6b2029
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 106 deletions.
68 changes: 34 additions & 34 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "datafusion-tui"
name = "dft"
description = "Terminal based, extensible, interactive data analysis tool using SQL "
homepage = "https://github.com/datafusion-contrib/datafusion-tui"
repository = "https://github.com/datafusion-contrib/datafusion-tui"
Expand All @@ -9,6 +9,7 @@ license = "Apache-2.0"
keywords = ["arrow", "query", "sql", "datafusion"]
version = "0.1.0"
edition = "2021"
default-run = "dft"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand Down
4 changes: 2 additions & 2 deletions src/app/handlers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use log::{error, info, trace};
use ratatui::crossterm::event::{self, KeyCode, KeyEvent};
use tui_logger::TuiWidgetEvent;

use crate::state::tabs::history::Context;
use crate::app::state::tabs::history::Context;

#[cfg(feature = "flightsql")]
use arrow_flight::sql::client::FlightSqlServiceClient;
Expand Down Expand Up @@ -208,7 +208,7 @@ pub fn app_event_handler(app: &mut App, event: AppEvent) -> Result<()> {
let url: &'static str = Box::leak(url.into_boxed_str());
let client = Arc::clone(&app.execution.flightsql_client);
tokio::spawn(async move {
let maybe_channel = Channel::from_static(&url).connect().await;
let maybe_channel = Channel::from_static(url).connect().await;
info!("Created channel");
match maybe_channel {
Ok(channel) => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub struct App<'app> {
}

impl<'app> App<'app> {
fn new(state: state::AppState<'app>, cli: DftCli) -> Self {
pub fn new(state: state::AppState<'app>, cli: DftCli) -> Self {
let (app_event_tx, app_event_rx) = mpsc::unbounded_channel();
let app_cancellation_token = CancellationToken::new();
let task = tokio::spawn(async {});
Expand Down
8 changes: 4 additions & 4 deletions src/app/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@

pub mod tabs;

use crate::app::cli;
use crate::app::config::get_data_dir;
use crate::app::state::tabs::sql::SQLTabState;
use crate::cli;
use crate::ui::SelectedTab;
use crate::app::ui::SelectedTab;
use log::{debug, error, info};
use std::path::PathBuf;

use self::tabs::{history::HistoryTabState, logs::LogsTabState};

use super::config::AppConfig;
#[cfg(feature = "flightsql")]
use crate::state::tabs::flightsql::FlightSQLTabState;
use crate::app::state::tabs::flightsql::FlightSQLTabState;

#[derive(Debug)]
pub struct Tabs {
Expand Down Expand Up @@ -56,7 +56,7 @@ pub struct AppState<'app> {
pub tabs: Tabs,
}

pub fn initialize(args: &cli::DftCli) -> AppState {
pub fn initialize<'app>(args: cli::DftCli) -> AppState<'app> {
debug!("Initializing state");
let data_dir = get_data_dir();
let config_path = args.get_config();
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod app;
pub mod cli;
pub mod telemetry;
pub mod ui;
13 changes: 5 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,18 @@
// specific language governing permissions and limitations
// under the License.

mod app;
mod cli;
mod telemetry;
mod ui;

use crate::app::state;
use app::{execute_files, run_app};
use clap::Parser;
use color_eyre::Result;
use dft::app::state;
use dft::app::{execute_files, run_app};
use dft::cli;
use dft::telemetry;

#[tokio::main]
async fn main() -> Result<()> {
telemetry::initialize_logs()?;
let cli = cli::DftCli::parse();
let state = state::initialize(&cli);
let state = state::initialize(cli.clone());

// If executing commands from files, do so and then exit
if !cli.file.is_empty() {
Expand Down
80 changes: 24 additions & 56 deletions tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
//! Tests for the CLI (e.g. run from files)
use assert_cmd::Command;
use predicates::str::ContainsPredicate;
use std::path::PathBuf;
use tempfile::NamedTempFile;

mod util;

#[test]
fn test_help() {
Expand All @@ -30,7 +30,7 @@ fn test_help() {
.assert()
.success();

assert.stdout(contains_str("dft"));
assert.stdout(util::contains_str("dft"));
}

#[test]
Expand All @@ -44,7 +44,7 @@ fn test_logging() {
.assert()
.success();

assert.stdout(contains_str("INFO"));
assert.stdout(util::contains_str("INFO"));
}

#[test]
Expand All @@ -57,12 +57,12 @@ fn test_command_in_file() {
+---------------------+
"##;

let file = sql_in_file("SELECT 1 + 1");
assert_output_contains(vec![file], expected);
let file = util::sql_in_file("SELECT 1 + 1");
util::assert_output_contains(vec![file], expected);

// same test but with a semicolon at the end
let file = sql_in_file("SELECT 1 + 1;");
assert_output_contains(vec![file], expected);
let file = util::sql_in_file("SELECT 1 + 1;");
util::assert_output_contains(vec![file], expected);
}

#[test]
Expand Down Expand Up @@ -91,12 +91,12 @@ CREATE TABLE foo as values (42);
SELECT column1 + 2 FROM foo
"#;

let file = sql_in_file(sql);
assert_output_contains(vec![file], expected);
let file = util::sql_in_file(sql);
util::assert_output_contains(vec![file], expected);

// same test but with a semicolon at the end of second command
let file = sql_in_file(format!("{sql};"));
assert_output_contains(vec![file], expected);
let file = util::sql_in_file(format!("{sql};"));
util::assert_output_contains(vec![file], expected);
}

#[test]
Expand All @@ -119,14 +119,14 @@ fn test_multiple_commands_in_multiple_files() {
+----------+
"##;

let file1 = sql_in_file("SELECT 1 + 2");
let file2 = sql_in_file("SELECT 1;\nselect 2;");
assert_output_contains(vec![file1, file2], expected);
let file1 = util::sql_in_file("SELECT 1 + 2");
let file2 = util::sql_in_file("SELECT 1;\nselect 2;");
util::assert_output_contains(vec![file1, file2], expected);
}

#[test]
fn test_non_existent_file() {
let file = sql_in_file("SELECT 1 + 1");
let file = util::sql_in_file("SELECT 1 + 1");
let p = PathBuf::from(file.path());
// dropping the file makes it non existent
drop(file);
Expand All @@ -139,13 +139,13 @@ fn test_non_existent_file() {
.failure();

let expected = format!("File does not exist: '{}'", p.to_string_lossy());
assert.code(2).stderr(contains_str(&expected));
assert.code(2).stderr(util::contains_str(&expected));
}

#[test]
fn test_one_existent_and_one_non_existent_file() {
let file1 = sql_in_file("SELECT 1 + 1");
let file2 = sql_in_file("SELECT 3 + 4");
let file1 = util::sql_in_file("SELECT 1 + 1");
let file2 = util::sql_in_file("SELECT 3 + 4");
let p1 = PathBuf::from(file1.path());
let p2 = PathBuf::from(file2.path());
// dropping the file makes it non existent
Expand All @@ -161,12 +161,12 @@ fn test_one_existent_and_one_non_existent_file() {
.failure();

let expected_err = format!("File does not exist: '{}'", p2.to_string_lossy());
assert.code(2).stderr(contains_str(&expected_err));
assert.code(2).stderr(util::contains_str(&expected_err));
}

#[test]
fn test_sql_err_in_file() {
let file = sql_in_file("SELECT this is not valid SQL");
let file = util::sql_in_file("SELECT this is not valid SQL");

let assert = Command::cargo_bin("dft")
.unwrap()
Expand All @@ -177,12 +177,12 @@ fn test_sql_err_in_file() {

let expected_err =
"Expected: [NOT] NULL or TRUE|FALSE or [NOT] DISTINCT FROM after IS, found: not";
assert.code(101).stderr(contains_str(expected_err));
assert.code(101).stderr(util::contains_str(expected_err));
}

#[test]
fn test_sql_err_in_file_after_first() {
let file = sql_in_file(
let file = util::sql_in_file(
r#"
-- First line is valid SQL
SELECT 1 + 1;
Expand All @@ -200,37 +200,5 @@ SELECT this is not valid SQL

let expected_err =
"Expected: [NOT] NULL or TRUE|FALSE or [NOT] DISTINCT FROM after IS, found: not";
assert.code(101).stderr(contains_str(expected_err));
}

/// Creates a temporary file with the given SQL content
fn sql_in_file(sql: impl AsRef<str>) -> NamedTempFile {
let file = NamedTempFile::new().unwrap();
std::fs::write(file.path(), sql.as_ref()).unwrap();
file
}

/// Returns a predicate that expects the given string to be contained in the
/// output
///
/// Whitespace is trimmed from the start and end of the string
fn contains_str(s: &str) -> ContainsPredicate {
predicates::str::contains(s.trim())
}

/// Invokes `dft -f` with the given files and asserts that it exited
/// successfully and the output contains the given string
fn assert_output_contains(files: Vec<NamedTempFile>, expected_output: &str) {
let mut cmd = Command::cargo_bin("dft").unwrap();
for file in &files {
cmd.arg("-f").arg(file.path());
}

let assert = cmd.assert().success();

// Since temp files are deleted when they go out of scope ensure they are
// dropped only after the command is run
drop(files);

assert.stdout(contains_str(expected_output));
assert.code(101).stderr(util::contains_str(expected_err));
}
Loading

0 comments on commit b6b2029

Please sign in to comment.