Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding rover dev smoke tests to our Test Suite #1961

Merged
merged 6 commits into from
Jul 8, 2024
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: 24 additions & 0 deletions .github/workflows/tests-mac-x86.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
description: 'JSON list of supergraph versions'
required: true
type: string
router-versions:
description: 'JSON list of router versions'
required: true
type: string

jobs:
build-rover:
Expand Down Expand Up @@ -67,3 +71,23 @@ jobs:
run: |
chmod +x ./artifact/{rover,xtask}
./artifact/xtask integration-test
smoke-tests:
needs: build-rover
name: Run simple smoke tests on macOS x86-64
strategy:
matrix:
composition-version: ${{ fromJSON(inputs.composition-versions) }}
router-version: ${{ fromJSON(inputs.router-versions) }}
# x86-64 runner
runs-on: macos-13
steps:
- uses: actions/checkout@v4
name: "Checkout rover repo"
- uses: actions/download-artifact@v4
name: "Download artifacts built in previous stages"
- uses: volta-cli/action@v4
name: "Install volta"
- name: Run Smoke Tests
run: |
chmod +x ./artifact/{rover,xtask}
./artifact/xtask smoke --binary-path ./artifact/rover --federation-version "${{ matrix.composition-version }}" --router-version "${{ matrix.router-version }}"
2 changes: 2 additions & 0 deletions Cargo.lock

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

2,287 changes: 571 additions & 1,716 deletions examples/supergraph-demo/pandas/package-lock.json

Large diffs are not rendered by default.

2,287 changes: 571 additions & 1,716 deletions examples/supergraph-demo/products/package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion examples/supergraph-demo/router.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
supergraph:
path: "/"

telemetry:
instrumentation:
spans:
mode: "spec_compliant"
2,049 changes: 536 additions & 1,513 deletions examples/supergraph-demo/users/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions xtask/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2021"
publish = false

[dependencies]
apollo-federation-types = { workspace = true }
assert_fs = { workspace = true }
anyhow = { workspace = true }
base64 = { workspace = true }
Expand All @@ -26,6 +27,7 @@ tar = { workspace = true }
regex = { workspace = true }
reqwest = { workspace = true, features = ["blocking", "native-tls", "json"] }
semver = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json_traversal = { workspace = true }
shell-candy = { workspace = true }
tempfile = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions xtask/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub(crate) use lint::Lint;
pub(crate) use package::Package;
pub(crate) use prep::Prep;
pub(crate) use security_check::SecurityCheck;
pub(crate) use smoke::Smoke;
pub(crate) use test::Test;
pub(crate) use unit_test::UnitTest;

Expand All @@ -15,6 +16,7 @@ pub(crate) mod lint;
pub(crate) mod package;
pub(crate) mod prep;
pub(crate) mod security_check;
pub(crate) mod smoke;
pub(crate) mod test;
pub(crate) mod unit_test;
pub(crate) mod version;
192 changes: 192 additions & 0 deletions xtask/src/commands/smoke.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
use std::collections::{HashMap, HashSet};
use std::process::{Child, Command};
use std::time::Duration;

use anyhow::{anyhow, Error};
use camino::Utf8PathBuf;
use clap::Parser;
use reqwest::Client;
use serde::Deserialize;
use serde_json::{json, Value};
use tokio::time::Instant;

use crate::tools::NpmRunner;
use crate::utils::PKG_PROJECT_ROOT;

#[derive(Debug, Parser)]
pub struct Smoke {
#[arg(long = "binary-path")]
pub(crate) binary_path: Utf8PathBuf,
#[arg(long = "federation-version")]
pub(crate) federation_version: Option<String>,
#[arg(long = "router-version")]
pub(crate) router_version: Option<String>,
}

#[derive(Debug, Deserialize)]
struct ReducedSuperGraphConfig {
subgraphs: HashMap<String, ReducedSubgraphConfig>,
}
#[derive(Debug, Deserialize)]
struct ReducedSubgraphConfig {
routing_url: String,
}

impl ReducedSuperGraphConfig {
pub fn get_subgraph_urls(self) -> Vec<String> {
self.subgraphs
.values()
.map(|x| x.routing_url.clone())
.collect()
}
}

const SUBGRAPH_TIMEOUT_DURATION: Duration = Duration::from_secs(15);
const ROUTER_TIMEOUT_DURATION: Duration = Duration::from_secs(30);

impl Smoke {
pub async fn run(&self) -> anyhow::Result<()> {
// Spin up the subgraphs first
crate::info!("Start subgraphs running...");
let npm_runner = NpmRunner::new()?;
let mut subgraph_handle = npm_runner.run_subgraphs()?;
// Wait for the subgraphs to respond correctly to an introspection request
crate::info!("Wait for subgraphs to become available...");
let client = Client::new();
Self::wait_for_subgraphs(&client).await?;
// Invoke Rover Dev
let port = 4000;
crate::info!("Run rover dev on port {}...", port);
let mut rover_dev_handle = self.run_rover_dev(
port,
self.federation_version.clone(),
self.router_version.clone(),
)?;
// Wait polling the router endpoint until that returns something sensible or times out
crate::info!("Wait for router to return a response...");
Self::wait_for_router(&client, port).await?;
// Close Up The Resources
crate::info!("Clean up resources...");
rover_dev_handle.kill()?;
subgraph_handle.kill()?;
Ok(())
}

async fn wait_for_router(client: &Client, port: u64) -> Result<(), Error> {
let federated_query = json!({"query": "{allPandas{ name favoriteFood } allProducts { createdBy { email name } package sku }}"});
let federated_response = json!({"data":{"allPandas":[{"name":"Basi","favoriteFood":"bamboo leaves"},{"name":"Yun","favoriteFood":"apple"}],"allProducts":[{"createdBy":{"email":"mara@acmecorp.com","name":"Mara"},"package":"@apollo/federation","sku":"federation"},{"createdBy":{"email":"mara@acmecorp.com","name":"Mara"},"package":"","sku":"studio"}]}});

let start = Instant::now();
loop {
let res = client
.post(format!("http://localhost:{}", port))
.json(&federated_query)
.send()
.await;
match res {
Ok(res) => {
if res.status().is_success() {
assert_eq!(res.json::<Value>().await?, federated_response);
return Ok(());
}
}
Err(_) => {
if start.elapsed() >= ROUTER_TIMEOUT_DURATION {
crate::info!(
"Could not connect to supergraph on port {} - Exiting...",
port
);
return Err(anyhow!("Failed to connect to supergraph."));
}
crate::info!(
"Could not connect to supergraph on port {} - Will retry",
port
);
tokio::time::sleep(Duration::from_secs(2)).await;
continue;
}
}
}
}

fn run_rover_dev(
&self,
port: u64,
federation_version: Option<String>,
router_version: Option<String>,
) -> Result<Child, Error> {
let project_root = PKG_PROJECT_ROOT.clone();
let supergraph_demo_directory = project_root.join("examples").join("supergraph-demo");
let mut cmd = Command::new(self.binary_path.canonicalize_utf8().unwrap());
cmd.args([
"dev",
"--supergraph-config",
"supergraph.yaml",
"--router-config",
"router.yaml",
"--supergraph-port",
&format!("{}", port),
"--elv2-license",
"accept",
])
.current_dir(supergraph_demo_directory);
if let Some(version) = federation_version {
cmd.env("APOLLO_ROVER_DEV_COMPOSITION_VERSION", version);
};
if let Some(version) = router_version {
cmd.env("APOLLO_ROVER_DEV_ROUTER_VERSION", version);
};
let rover_dev_handle = cmd.spawn()?;
Ok(rover_dev_handle)
}

async fn wait_for_subgraphs(client: &Client) -> Result<(), Error> {
let introspection_query = json!({"query": "{__schema{types{name}}}"});
let mut finished = HashSet::new();

// Read in the supergraph YAML file, so we can extract the routing URLs, this way we
// don't need to hardcode any of the port values etc.
let project_root = PKG_PROJECT_ROOT.clone();
let supergraph_yaml_path = project_root
.join("examples")
.join("supergraph-demo/supergraph.yaml");
let urls = Self::get_subgraph_urls(supergraph_yaml_path);

// Loop over the URLs
let start = Instant::now();
loop {
for url in urls.iter() {
let res = client.post(url).json(&introspection_query).send().await;
match res {
Ok(res) => {
if res.status().is_success() {
finished.insert(url);
}
}
Err(e) => {
crate::info!(
"Could not connect to subgraph on {}: {:} - Will retry",
url,
e
);
tokio::time::sleep(Duration::from_secs(2)).await;
}
}
}

if finished.len() == urls.len() {
return Ok(());
}
if start.elapsed() >= SUBGRAPH_TIMEOUT_DURATION {
crate::info!("Could not connect to all subgraphs. Exiting...");
return Err(anyhow!("Could not connect to all subgraphs"));
}
}
}

fn get_subgraph_urls(supergraph_yaml_path: Utf8PathBuf) -> Vec<String> {
let content = std::fs::read_to_string(supergraph_yaml_path).unwrap();
let sc_config: ReducedSuperGraphConfig = serde_yaml::from_str(&content).unwrap();
sc_config.get_subgraph_urls()
}
}
4 changes: 4 additions & 0 deletions xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ pub enum Command {

/// Run supergraph-demo with a local Rover build
IntegrationTest(commands::IntegrationTest),

/// Run a basic smoke test for rover dev
Smoke(commands::Smoke),
}

impl Xtask {
Expand All @@ -64,6 +67,7 @@ impl Xtask {
Command::Prep(command) => command.run(),
Command::Package(command) => command.run(),
Command::SecurityChecks(command) => command.run(),
Command::Smoke(command) => tokio::runtime::Runtime::new()?.block_on(command.run()),
}?;
eprintln!("{}", style("Success!").green().bold());
Ok(())
Expand Down
29 changes: 24 additions & 5 deletions xtask/src/tools/npm.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::process::{Child, Command};
use std::{fs, str};

use anyhow::{anyhow, Context, Result};
use camino::Utf8PathBuf;
use rover_std::Fs;
use which::which;

use std::{fs, str};
use rover_std::Fs;

use crate::info;
use crate::{
Expand All @@ -16,6 +18,7 @@ pub(crate) struct NpmRunner {
npm_installer_package_directory: Utf8PathBuf,
rover_client_lint_directory: Utf8PathBuf,
flyby_directory: Utf8PathBuf,
supergraph_demo_directory: Utf8PathBuf,
}

impl NpmRunner {
Expand All @@ -26,6 +29,7 @@ impl NpmRunner {
let rover_client_lint_directory = project_root.join("crates").join("rover-client");
let npm_installer_package_directory = project_root.join("installers").join("npm");
let flyby_directory = project_root.join("examples").join("flyby");
let supergraph_demo_directory = project_root.join("examples").join("supergraph-demo");

if !npm_installer_package_directory.exists() {
return Err(anyhow!(
Expand All @@ -48,10 +52,10 @@ impl NpmRunner {
));
}

if !flyby_directory.exists() {
if !supergraph_demo_directory.exists() {
return Err(anyhow!(
"Rover's example flyby directory does not seem to be located here:\n{}",
&flyby_directory
"Rover's example supergraph-demo directory does not seem to be located here:\n{}",
&supergraph_demo_directory
));
}

Expand All @@ -60,6 +64,7 @@ impl NpmRunner {
npm_installer_package_directory,
rover_client_lint_directory,
flyby_directory,
supergraph_demo_directory,
})
}

Expand Down Expand Up @@ -141,6 +146,20 @@ impl NpmRunner {
}
}

pub(crate) fn run_subgraphs(&self) -> Result<Child> {
self.require_volta()?;
// Run the installation scripts synchronously, because they will run to completion
self.npm_exec(&["install"], &self.supergraph_demo_directory)?;
self.npm_exec(&["run", "postinstall"], &self.supergraph_demo_directory)?;
// Then kick off the subgraph processes and return the handle so that we can kill it later
// on
let mut cmd = Command::new("npm");
cmd.arg("start")
.current_dir(&self.supergraph_demo_directory);
let handle = cmd.spawn()?;
Ok(handle)
}

fn require_volta(&self) -> Result<()> {
which("volta")
.map(|_| ())
Expand Down