Skip to content

Commit

Permalink
feat: add test for avatar image generation
Browse files Browse the repository at this point in the history
move scenes snapshots to it's own folder
  • Loading branch information
kuruk-mm committed Oct 18, 2024
1 parent 3e6ed18 commit c1cfae9
Show file tree
Hide file tree
Showing 92 changed files with 189 additions and 13 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,9 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: snapshots
path: tests/snapshots/comparison/**/*
path: |
tests/snapshots/scenes/comparison/**/*
tests/snapshots/avatar-image-generation/comparison/**/*
build:
name: Build and test
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ reqwest = { version = "0.11", default-features = false, features = ["json", "rus
serde_json = { version = "1.0.92", features = ["raw_value"] }
glob = "*"
tokio = { version = "1.26.0", features = ["sync", "rt-multi-thread", "net"] }
directories = "5.0.1"
directories = "5.0.1"
image = "0.25.4"
5 changes: 4 additions & 1 deletion godot/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ mono_crash.*.json
# System/tool-specific ignores
.directory
.DS_Store
*~
*~

# Decentraland ignores
/output/
4 changes: 2 additions & 2 deletions godot/src/test/testing_api.gd
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ func start():
snapshot_folder = args[snapshot_folder_index + 1]
else:
if OS.has_feature("editor"):
snapshot_folder = ProjectSettings.globalize_path("res://../tests/snapshots")
snapshot_folder = ProjectSettings.globalize_path("res://../tests/snapshots/scenes")
else:
snapshot_folder = OS.get_user_data_dir() + "/snapshot"
snapshot_folder = OS.get_user_data_dir() + "/snapshot/scenes"

if not snapshot_folder.ends_with("/"):
snapshot_folder += "/"
Expand Down
5 changes: 3 additions & 2 deletions godot/src/tool/avatar_renderer/avatar_renderer_standalone.gd
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ var current_avatar: DclAvatarWireFormat

# TODO: this can be a command line parser and get some helpers like get_string("--realm"), etc
func get_params_from_cmd():
if USE_TEST_INPUT:
var args := OS.get_cmdline_args()

if USE_TEST_INPUT or args.has("--use-test-input"):
return [
AvatarRendererHelper.AvatarFile.from_file_path(
"res://src/tool/avatar_renderer/test-input.json"
)
]

var args := OS.get_cmdline_args()
var avatar_data = null
var avatar_in_place := args.find("--avatars")

Expand Down
27 changes: 27 additions & 0 deletions src/copy_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,30 @@ pub fn copy_ffmpeg_libraries(dest_folder: String, link_libs: bool) -> Result<(),
}
Ok(())
}

// Function to move the directory and its contents recursively
pub fn move_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> {
// Check if destination exists, create it if it doesn't
if !dest.exists() {
fs::create_dir_all(dest)?; // Create the destination directory
}

for entry in fs::read_dir(src)? {
let entry = entry?;
let path = entry.path();
let dest_path = dest.join(entry.file_name());

if path.is_dir() {
// Recursively move subdirectory
move_dir_recursive(&path, &dest_path)?;
} else {
// Move file
fs::rename(&path, &dest_path)?; // Move the file
}
}

// Remove the source directory after moving all contents
fs::remove_dir_all(src)?;

Ok(())
}
90 changes: 90 additions & 0 deletions src/image_comparison.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use image::{GenericImageView, Pixel};
use std::fs;
use std::path::{Path, PathBuf};

// Function to compare two images and calculate similarity
pub fn compare_images_similarity(image_path_1: &Path, image_path_2: &Path) -> Result<f64, String> {
let img1 = image::open(image_path_1).map_err(|_| format!("Failed to open image: {:?}", image_path_1))?;
let img2 = image::open(image_path_2).map_err(|_| format!("Failed to open image: {:?}", image_path_2))?;

if img1.dimensions() != img2.dimensions() {
return Err("Images have different dimensions".to_string());
}

let (width, height) = img1.dimensions();
let mut total_diff = 0.0;
let mut total_pixels = 0.0;

for y in 0..height {
for x in 0..width {
let pixel1 = img1.get_pixel(x, y);
let pixel2 = img2.get_pixel(x, y);

let diff = pixel1.channels().iter()
.zip(pixel2.channels().iter())
.map(|(p1, p2)| (*p1 as f64 - *p2 as f64).powi(2))
.sum::<f64>();

total_diff += diff.sqrt();
total_pixels += 1.0;
}
}

let max_diff_per_pixel = (255.0_f64 * 3.0_f64).sqrt();
let average_diff = total_diff / total_pixels;
let similarity = 1.0 - (average_diff / max_diff_per_pixel);

Ok(similarity)
}

// Function to list all PNG files in a directory
fn list_png_files(directory: &Path) -> Result<Vec<PathBuf>, String> {
let mut files = vec![];

for entry in fs::read_dir(directory).map_err(|_| format!("Failed to read directory: {:?}", directory))? {
let entry = entry.map_err(|_| "Failed to access entry in directory".to_string())?;
let path = entry.path();
if path.extension().and_then(|ext| ext.to_str()) == Some("png") {
files.push(path);
}
}

files.sort(); // Ensure files are in the same order
Ok(files)
}

// Function to compare all PNG files in two folders
pub fn compare_images_folders(snapshot_folder: &Path, result_folder: &Path, similarity_threshold: f64) -> Result<(), String> {
let snapshot_files = list_png_files(snapshot_folder)?;
let result_files = list_png_files(result_folder)?;

// Ensure both folders have the same number of files
if snapshot_files.len() != result_files.len() {
return Err("Snapshot and result folders contain different numbers of files".to_string());
}

// Compare each corresponding file
for (snapshot_file, result_file) in snapshot_files.iter().zip(result_files.iter()) {
let similarity = compare_images_similarity(snapshot_file, result_file)?;

// If similarity is less than the `similarity_threshold`, the test fails
if similarity < similarity_threshold {
return Err(format!(
"Files {:?} and {:?} are too different! Similarity: {:.5}%",
snapshot_file,
result_file,
similarity * 100.0
));
}

println!(
"Files {:?} and {:?} are {:.5}% similar.",
snapshot_file,
result_file,
similarity * 100.0
);
}

println!("All files match with 99.95% similarity or higher!");
Ok(())
}
19 changes: 13 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{collections::HashMap, fs::create_dir_all, path::Path};

use anyhow::Context;
use clap::{AppSettings, Arg, Command};
use tests::test_godot_tools;
use xtaskops::ops::{clean_files, cmd, confirm, remove_dir};

use crate::consts::RUST_LIB_PROJECT_FOLDER;
Expand All @@ -13,6 +14,8 @@ mod export;
mod install_dependency;
mod path;
mod run;
mod tests;
mod image_comparison;

fn main() -> Result<(), anyhow::Error> {
let cli = Command::new("xtask")
Expand All @@ -26,6 +29,7 @@ fn main() -> Result<(), anyhow::Error> {
.takes_value(false),
),
)
.subcommand(Command::new("test-tools"))
.subcommand(Command::new("vars"))
.subcommand(Command::new("ci"))
.subcommand(Command::new("powerset"))
Expand Down Expand Up @@ -77,9 +81,9 @@ fn main() -> Result<(), anyhow::Error> {
.takes_value(false),
)
.arg(
Arg::new("itest")
.long("itest")
.help("run integration-tests")
Arg::new("test-godot-tools")
.long("test-godot-tools")
.help("run godot tools (like avatar image generation)")
.takes_value(false),
)
.arg(
Expand Down Expand Up @@ -155,6 +159,7 @@ fn main() -> Result<(), anyhow::Error> {
}
("export", _m) => export::export(),
("coverage", sm) => coverage_with_itest(sm.is_present("dev")),
("test-tools", _) => test_godot_tools(None),
("vars", _) => {
println!("root: {root:?}");
Ok(())
Expand All @@ -181,8 +186,8 @@ fn main() -> Result<(), anyhow::Error> {
}

pub fn coverage_with_itest(devmode: bool) -> Result<(), anyhow::Error> {
let snapshot_folder = Path::new("./tests/snapshots");
let snapshot_folder = snapshot_folder.canonicalize()?;
let scene_snapshot_folder = Path::new("./tests/snapshots/scenes");
let scene_snapshot_folder = scene_snapshot_folder.canonicalize()?;

remove_dir("./coverage")?;
create_dir_all("./coverage")?;
Expand Down Expand Up @@ -245,7 +250,7 @@ pub fn coverage_with_itest(devmode: bool) -> Result<(), anyhow::Error> {
"--realm",
scene_test_realm,
"--snapshot-folder",
snapshot_folder.to_str().unwrap(),
scene_snapshot_folder.to_str().unwrap(),
]
.iter()
.map(|it| it.to_string())
Expand All @@ -263,6 +268,8 @@ pub fn coverage_with_itest(devmode: bool) -> Result<(), anyhow::Error> {
Some(build_envs.clone()),
)?;

test_godot_tools(Some(build_envs))?;

let err = glob::glob("./godot/*.profraw")?
.filter_map(|entry| entry.ok())
.map(|entry| {
Expand Down
45 changes: 45 additions & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use std::{collections::HashMap, path::Path};

use anyhow::Ok;

use crate::{copy_files::move_dir_recursive, image_comparison::compare_images_folders, run};

pub fn test_godot_tools(with_build_envs: Option<HashMap<String, String>>) -> Result<(), anyhow::Error> {
let avatar_snapshot_folder = Path::new("./tests/snapshots/avatar-image-generation").canonicalize()?;
let comparison_folder = avatar_snapshot_folder.join("comparison");

println!("=== running godot avatar generation ===");

let extra_args = [
"--rendering-driver",
"opengl3",
"--avatar-renderer",
"--use-test-input",
]
.iter()
.map(|it| it.to_string())
.collect();

run::run(
false,
false,
false,
false,
false,
false,
vec![],
extra_args,
with_build_envs,
)?;

// Move files
let avatar_output = Path::new("./godot/output/").canonicalize()?;
move_dir_recursive(&avatar_output, &comparison_folder)?;

// Images comparison
compare_images_folders(&avatar_snapshot_folder, &comparison_folder, 0.995)
.map_err(|e| anyhow::anyhow!(e))?;


Ok(())
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit c1cfae9

Please sign in to comment.