Skip to content

Commit

Permalink
Drastically simplify test helpers (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
CosmicHorrorDev authored Nov 19, 2023
1 parent 067ba09 commit ad15e99
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 97 deletions.
6 changes: 2 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,9 @@ pub use libraryfolders::{parse_library_folders, Library};
pub mod shortcut;
pub use shortcut::Shortcut;

/// NOT A PART OF THE PUBLIC API
///
/// These are just some helpers for setting up dummy test environments
#[doc(hidden)]
pub mod __test_helpers;
#[cfg(any(test, doctest))]
pub mod test_helpers;

/// An instance of a Steam installation.
///
Expand Down
126 changes: 35 additions & 91 deletions src/__test_helpers.rs → src/test_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,122 +1,82 @@
//! NOT PART OF THE PUBLIC API
//!
//! Some test helpers for setting up isolated dummy steam installations.
//!
//! Publicly accessible so that we can use them in unit, doc, and integration tests.

// TODO: add a test with an env var flag that runs against your real local steam installation?

use std::{
collections::BTreeMap,
convert::{TryFrom, TryInto},
fs, iter,
marker::PhantomData,
path::{Path, PathBuf},
};

use crate::SteamDir;

use serde::Serialize;
use tempfile::TempDir;

// A little bit of a headache. We want to use tempdirs for isolating the dummy steam installations,
// but we can't specify a `cfg` that includes integration tests while also allowing for naming a
// `dev-dependency` here. Instead we abstract the functionality behind a trait and every dependent
// can provide their own concrete implementation. It makes for a bit of a mess unfortunately, but
// it's either this or add a feature that's only used internally for testing which I don't like
// even more.
pub trait TempDir: Sized {
fn new() -> Result<Self, TestError>;
fn path(&self) -> PathBuf;
}

#[cfg(test)]
pub struct TestTempDir(tempfile::TempDir);

#[cfg(test)]
impl TempDir for TestTempDir {
fn new() -> Result<Self, TestError> {
let mut builder = tempfile::Builder::new();
builder.prefix("steamlocate-test-");
let temp_dir = builder.tempdir()?;
Ok(Self(temp_dir))
}

fn path(&self) -> PathBuf {
self.0.path().to_owned()
}
fn test_temp_dir() -> Result<TempDir, TestError> {
let temp_dir = tempfile::Builder::new()
.prefix("steamlocate-test-")
.tempdir()?;
Ok(temp_dir)
}

pub type TestError = Box<dyn std::error::Error>;
pub type TestResult = Result<(), TestError>;

// TODO(cosmic): Add in functionality for providing shortcuts too
pub struct TempSteamDir<TmpDir> {
pub struct TempSteamDir {
steam_dir: crate::SteamDir,
_tmps: Vec<TmpDir>,
_tmps: Vec<TempDir>,
}

impl<TmpDir: TempDir> TryFrom<AppFile> for TempSteamDir<TmpDir> {
impl TryFrom<AppFile> for TempSteamDir {
type Error = TestError;

fn try_from(app: AppFile) -> Result<Self, Self::Error> {
Self::builder().app(app).finish()
}
}

impl<TmpDir: TempDir> TryFrom<SampleApp> for TempSteamDir<TmpDir> {
impl TryFrom<SampleApp> for TempSteamDir {
type Error = TestError;

fn try_from(sample_app: SampleApp) -> Result<Self, Self::Error> {
Self::try_from(AppFile::from(sample_app))
}
}

impl<TmpDir> TempSteamDir<TmpDir> {
pub fn builder() -> TempSteamDirBuilder<TmpDir> {
TempSteamDirBuilder::new()
impl TempSteamDir {
pub fn builder() -> TempSteamDirBuilder {
TempSteamDirBuilder::default()
}

pub fn steam_dir(&self) -> &SteamDir {
&self.steam_dir
}
}

#[derive(Default)]
#[must_use]
pub struct TempSteamDirBuilder<TmpDir> {
libraries: Vec<TempLibrary<TmpDir>>,
pub struct TempSteamDirBuilder {
libraries: Vec<TempLibrary>,
apps: Vec<AppFile>,
}

impl<TmpDir> Default for TempSteamDirBuilder<TmpDir> {
fn default() -> Self {
Self {
libraries: Vec::default(),
apps: Vec::default(),
}
}
}

impl<TmpDir> TempSteamDirBuilder<TmpDir> {
pub fn new() -> Self {
Self::default()
}

impl TempSteamDirBuilder {
pub fn app(mut self, app: AppFile) -> Self {
self.apps.push(app);
self
}

pub fn library(mut self, library: TempLibrary<TmpDir>) -> Self {
pub fn library(mut self, library: TempLibrary) -> Self {
self.libraries.push(library);
self
}

// Steam dir is also a library, but is laid out slightly differently than a regular library
pub fn finish(self) -> Result<TempSteamDir<TmpDir>, TestError>
where
TmpDir: TempDir,
{
let tmp = TmpDir::new()?;
pub fn finish(self) -> Result<TempSteamDir, TestError> {
let tmp = test_temp_dir()?;
let root_dir = tmp.path().join("test-steam-dir");
let steam_dir = root_dir.join("Steam");
let apps_dir = steam_dir.join("steamapps");
Expand Down Expand Up @@ -155,10 +115,10 @@ fn setup_steamapps_dir(apps_dir: &Path, apps: &[AppFile]) -> Result<(), TestErro
Ok(())
}

fn setup_libraryfolders_file<TmpDir>(
fn setup_libraryfolders_file(
apps_dir: &Path,
root_library: LibraryFolder,
aux_libraries: &[TempLibrary<TmpDir>],
aux_libraries: &[TempLibrary],
) -> Result<(), TestError> {
let library_folders =
iter::once(root_library).chain(aux_libraries.iter().map(|temp_library| {
Expand Down Expand Up @@ -207,65 +167,49 @@ impl LibraryFolder {
}
}

pub struct TempLibrary<TmpDir> {
pub struct TempLibrary {
content_id: i32,
path: PathBuf,
apps: BTreeMap<u32, u64>,
_tmp: TmpDir,
_tmp: TempDir,
}

impl<TmpDir: TempDir> TryFrom<AppFile> for TempLibrary<TmpDir> {
impl TryFrom<AppFile> for TempLibrary {
type Error = TestError;

fn try_from(app: AppFile) -> Result<Self, Self::Error> {
Self::builder().app(app).finish()
}
}

impl<TmpDir: TempDir> TryFrom<SampleApp> for TempLibrary<TmpDir> {
impl TryFrom<SampleApp> for TempLibrary {
type Error = TestError;

fn try_from(sample_app: SampleApp) -> Result<Self, Self::Error> {
Self::try_from(AppFile::from(sample_app))
}
}

impl<TmpDir> TempLibrary<TmpDir> {
pub fn builder() -> TempLibraryBuilder<TmpDir> {
TempLibraryBuilder::new()
impl TempLibrary {
pub fn builder() -> TempLibraryBuilder {
TempLibraryBuilder::default()
}
}

#[derive(Default)]
#[must_use]
pub struct TempLibraryBuilder<TmpDir> {
pub struct TempLibraryBuilder {
apps: Vec<AppFile>,
temp_dir_type: PhantomData<TmpDir>,
}

impl<TmpDir> Default for TempLibraryBuilder<TmpDir> {
fn default() -> Self {
Self {
apps: Vec::default(),
temp_dir_type: PhantomData,
}
}
}

impl<TmpDir> TempLibraryBuilder<TmpDir> {
fn new() -> Self {
Self::default()
}

impl TempLibraryBuilder {
fn app(mut self, app: AppFile) -> Self {
self.apps.push(app);
self
}

fn finish(self) -> Result<TempLibrary<TmpDir>, TestError>
where
TmpDir: TempDir,
{
let tmp = TmpDir::new()?;
fn finish(self) -> Result<TempLibrary, TestError> {
let tmp = test_temp_dir()?;
let root_dir = tmp.path().join("test-library");
let apps_dir = root_dir.join("steamapps");
fs::create_dir_all(&apps_dir)?;
Expand Down Expand Up @@ -343,7 +287,7 @@ impl SampleApp {

#[test]
fn sanity() -> TestResult {
let tmp_steam_dir = TempSteamDir::<TestTempDir>::try_from(SampleApp::GarrysMod)?;
let tmp_steam_dir = TempSteamDir::try_from(SampleApp::GarrysMod)?;
let steam_dir = tmp_steam_dir.steam_dir();
assert!(steam_dir.app(SampleApp::GarrysMod.id()).unwrap().is_some());

Expand Down
4 changes: 2 additions & 2 deletions src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::convert::TryInto;

use crate::{
test_helpers::{SampleApp, TempSteamDir, TestError, TestResult},
Error,
__test_helpers::{SampleApp, TempSteamDir, TestError, TestResult, TestTempDir},
};

static GMOD_ID: u32 = SampleApp::GarrysMod.id();
Expand All @@ -11,7 +11,7 @@ static GMOD_ID: u32 = SampleApp::GarrysMod.id();
// - Steam must be installed
// - At least two library folders must be setup (the steam dir acts as one)
// - Garry's Mod along with at least one other steam app must be installed
pub fn legacy_test_env() -> std::result::Result<TempSteamDir<TestTempDir>, TestError> {
pub fn legacy_test_env() -> std::result::Result<TempSteamDir, TestError> {
TempSteamDir::builder()
.app(SampleApp::GarrysMod.into())
.library(SampleApp::GraveyardKeeper.try_into()?)
Expand Down

0 comments on commit ad15e99

Please sign in to comment.