tests: Make tests use a virtual filesystem, which currently maps to the regular physical FS rooted in a test directory

This commit is contained in:
Nathan Adams 2023-11-12 22:57:44 +01:00
parent c7a1e1178a
commit d6f16b0be5
23 changed files with 789 additions and 114 deletions

8
Cargo.lock generated
View File

@ -4379,6 +4379,7 @@ dependencies = [
"env_logger",
"futures",
"image",
"percent-encoding",
"pretty_assertions",
"regex",
"ruffle_core",
@ -4391,6 +4392,7 @@ dependencies = [
"tracing",
"tracing-subscriber",
"url",
"vfs",
]
[[package]]
@ -5563,6 +5565,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vfs"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e4fe92cfc1bad19c19925d5eee4b30584dbbdee4ff10183b261acccbef74e2d"
[[package]]
name = "vswhom"
version = "0.1.0"

View File

@ -533,8 +533,7 @@ pub fn resolve_url_with_relative_base_path<NavigatorType: NavigatorBackend>(
}
}
/// This is the fetch implementation for the TestNavigatorBackend and the
/// NullNavigatorBackend.
/// This is the fetch implementation for NullNavigatorBackend.
///
/// It tries to fetch the given URL as a local path and read and return
/// its content. It returns an ErrorResponse if the URL is not valid, not

View File

@ -27,6 +27,8 @@ serde = "1.0.192"
toml = "0.8.8"
anyhow = "1.0.75"
async-channel = "1.9.0"
vfs = "0.10.0"
percent-encoding = "2.3.0"
[features]
jpegxr = ["ruffle_core/jpegxr"]

View File

@ -1,18 +1,20 @@
use crate::backends::TestLogBackend;
use crate::util::read_bytes;
use async_channel::Receiver;
use percent_encoding::percent_decode_str;
use ruffle_core::backend::log::LogBackend;
use ruffle_core::backend::navigator::{
fetch_path, resolve_url_with_relative_base_path, ErrorResponse, NavigationMethod,
NavigatorBackend, NullExecutor, NullSpawner, OwnedFuture, Request, SuccessResponse,
async_return, create_fetch_error, ErrorResponse, NavigationMethod, NavigatorBackend,
NullExecutor, NullSpawner, OwnedFuture, Request, SuccessResponse,
};
use ruffle_core::indexmap::IndexMap;
use ruffle_core::loader::Error;
use ruffle_core::socket::{ConnectionState, SocketAction, SocketHandle};
use ruffle_socket_format::SocketEvent;
use std::path::{Path, PathBuf};
use std::sync::mpsc::Sender;
use std::time::Duration;
use url::{ParseError, Url};
use vfs::VfsPath;
/// A `NavigatorBackend` used by tests that supports logging fetch requests.
///
@ -27,21 +29,21 @@ use url::{ParseError, Url};
/// URLs can be used in Flash Player when writing tests
pub struct TestNavigatorBackend {
spawner: NullSpawner,
relative_base_path: PathBuf,
relative_base_path: VfsPath,
socket_events: Option<Vec<SocketEvent>>,
log: Option<TestLogBackend>,
}
impl TestNavigatorBackend {
pub fn new(
path: &Path,
path: VfsPath,
executor: &NullExecutor,
socket_events: Option<Vec<SocketEvent>>,
log: Option<TestLogBackend>,
) -> Result<Self, std::io::Error> {
Ok(Self {
spawner: executor.spawner(),
relative_base_path: path.canonicalize()?,
relative_base_path: path,
socket_events,
log,
})
@ -125,16 +127,86 @@ impl NavigatorBackend for TestNavigatorBackend {
}
}
fetch_path(
self,
"TestNavigatorBackend",
request.url(),
Some(&self.relative_base_path),
)
let url = match self.resolve_url(request.url()) {
Ok(url) => url,
Err(e) => return async_return(create_fetch_error(request.url(), e)),
};
let base_path = self.relative_base_path.clone();
Box::pin(async move {
let path = if url.scheme() == "file" {
// Flash supports query parameters with local urls.
// SwfMovie takes care of exposing those to ActionScript -
// when we actually load a filesystem url, strip them out.
let mut filesystem_url = url.clone();
filesystem_url.set_query(None);
base_path
.join(
percent_decode_str(filesystem_url.path())
.decode_utf8()
.map_err(|e| ErrorResponse {
url: url.to_string(),
error: Error::FetchError(e.to_string()),
})?,
)
.map_err(|e| ErrorResponse {
url: url.to_string(),
error: Error::FetchError(e.to_string()),
})?
} else {
// Turn a url like https://localhost/foo/bar to {base_path}/localhost/foo/bar
let mut path = base_path.clone();
if let Some(host) = url.host_str() {
path = path.join(host).map_err(|e| ErrorResponse {
url: url.to_string(),
error: Error::FetchError(e.to_string()),
})?;
}
if let Some(remaining) = url.path().strip_prefix('/') {
path = path
.join(percent_decode_str(remaining).decode_utf8().map_err(|e| {
ErrorResponse {
url: url.to_string(),
error: Error::FetchError(e.to_string()),
}
})?)
.map_err(|e| ErrorResponse {
url: url.to_string(),
error: Error::FetchError(e.to_string()),
})?;
}
path
};
let body = read_bytes(&path).map_err(|error| ErrorResponse {
url: url.to_string(),
error: Error::FetchError(error.to_string()),
})?;
Ok(SuccessResponse {
url: url.to_string(),
body,
status: 0,
redirected: false,
})
})
}
fn resolve_url(&self, url: &str) -> Result<Url, ParseError> {
resolve_url_with_relative_base_path(self, self.relative_base_path.clone(), url)
let mut base_url = Url::parse("file:///")?;
// Make sure we have a trailing slash, so that joining a request url like 'data.txt'
// gets appended, rather than replacing the last component.
base_url.path_segments_mut().unwrap().push("");
if let Ok(parsed_url) = base_url.join(url) {
Ok(self.pre_process_url(parsed_url))
} else {
match Url::parse(url) {
Ok(parsed_url) => Ok(self.pre_process_url(parsed_url)),
Err(error) => Err(error),
}
}
}
fn spawn_future(&mut self, future: OwnedFuture<(), Error>) {

View File

@ -5,7 +5,10 @@ pub mod options;
pub mod runner;
pub mod test;
pub use vfs;
mod backends;
mod util;
pub fn set_logger() {
let _ = env_logger::Builder::from_env(

View File

@ -1,8 +1,10 @@
use crate::backends::TestAudioBackend;
use crate::environment::{Environment, RenderInterface};
use crate::image_trigger::ImageTrigger;
use crate::util::write_image;
use anyhow::{anyhow, Result};
use approx::assert_relative_eq;
use image::ImageOutputFormat;
use regex::Regex;
use ruffle_core::tag_utils::SwfMovie;
use ruffle_core::{PlayerBuilder, ViewportDimensions};
@ -10,9 +12,8 @@ use ruffle_render::backend::RenderBackend;
use ruffle_render::quality::StageQuality;
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::{Path, PathBuf};
use std::time::Duration;
use vfs::VfsPath;
#[derive(Deserialize)]
#[serde(default, deny_unknown_fields)]
@ -20,7 +21,7 @@ pub struct TestOptions {
pub num_frames: Option<u32>,
pub num_ticks: Option<u32>,
pub tick_rate: Option<f64>,
pub output_path: PathBuf,
pub output_path: String,
pub sleep_to_meet_frame_rate: bool,
pub image_comparisons: HashMap<String, ImageComparison>,
pub ignore: bool,
@ -37,7 +38,7 @@ impl Default for TestOptions {
num_frames: None,
num_ticks: None,
tick_rate: None,
output_path: PathBuf::from("output.txt"),
output_path: "output.txt".to_string(),
sleep_to_meet_frame_rate: false,
image_comparisons: Default::default(),
ignore: false,
@ -51,8 +52,8 @@ impl Default for TestOptions {
}
impl TestOptions {
pub fn read<P: AsRef<Path>>(path: P) -> Result<Self> {
let result: Self = toml::from_str(&fs::read_to_string(path)?)?;
pub fn read(path: &VfsPath) -> Result<Self> {
let result: Self = toml::from_str(&path.read_to_string()?)?;
result.validate()?;
Ok(result)
}
@ -75,8 +76,8 @@ impl TestOptions {
Ok(())
}
pub fn output_path(&self, test_directory: &Path) -> PathBuf {
test_directory.join(&self.output_path)
pub fn output_path(&self, test_directory: &VfsPath) -> Result<VfsPath> {
Ok(test_directory.join(&self.output_path)?)
}
}
@ -216,7 +217,7 @@ impl ImageComparison {
name: &str,
actual_image: image::RgbaImage,
expected_image: image::RgbaImage,
test_path: &Path,
test_path: &VfsPath,
environment_name: String,
known_failure: bool,
) -> Result<()> {
@ -225,9 +226,11 @@ impl ImageComparison {
let save_actual_image = || {
if !known_failure {
// If we're expecting failure, spamming files isn't productive.
actual_image
.save(test_path.join(format!("{name}.actual-{environment_name}.png")))
.context("Couldn't save actual image")
write_image(
&test_path.join(format!("{name}.actual-{environment_name}.png"))?,
&actual_image,
ImageOutputFormat::Png,
)
} else {
Ok(())
}
@ -295,14 +298,17 @@ impl ImageComparison {
if !known_failure {
// If we're expecting failure, spamming files isn't productive.
image::RgbImage::from_raw(
let difference_image = image::RgbImage::from_raw(
actual_image.width(),
actual_image.height(),
difference_color,
)
.context("Couldn't create color difference image")?
.save(test_path.join(format!("{name}.difference-color-{environment_name}.png")))
.context("Couldn't save color difference image")?;
.context("Couldn't create color difference image")?;
write_image(
&test_path.join(format!("{name}.difference-color-{environment_name}.png"))?,
&difference_image,
ImageOutputFormat::Png,
)?;
}
if is_alpha_different {
@ -315,14 +321,18 @@ impl ImageComparison {
if !known_failure {
// If we're expecting failure, spamming files isn't productive.
image::GrayImage::from_raw(
let difference_image = image::GrayImage::from_raw(
actual_image.width(),
actual_image.height(),
difference_alpha,
)
.context("Couldn't create alpha difference image")?
.save(test_path.join(format!("{name}.difference-alpha-{environment_name}.png")))
.context("Couldn't save alpha difference image")?;
.context("Couldn't create alpha difference image")?;
write_image(
&test_path
.join(format!("{name}.difference-alpha-{environment_name}.png"))?,
&difference_image,
ImageOutputFormat::Png,
)?;
}
}

View File

@ -4,7 +4,9 @@ use crate::fs_commands::{FsCommand, TestFsCommandProvider};
use crate::image_trigger::ImageTrigger;
use crate::options::ImageComparison;
use crate::test::Test;
use crate::util::{read_bytes, write_image};
use anyhow::{anyhow, Result};
use image::ImageOutputFormat;
use ruffle_core::backend::navigator::NullExecutor;
use ruffle_core::events::MouseButton as RuffleMouseButton;
use ruffle_core::events::{KeyCode, TextControlCode as RuffleTextControlCode};
@ -17,9 +19,9 @@ use ruffle_input_format::{
};
use ruffle_render::backend::{RenderBackend, ViewportDimensions};
use ruffle_socket_format::SocketEvent;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use vfs::VfsPath;
/// Loads an SWF and runs it through the Ruffle core for a number of frames.
/// Tests that the trace output matches the given expected output.
@ -34,7 +36,6 @@ pub fn run_swf(
renderer: Option<(Box<dyn RenderInterface>, Box<dyn RenderBackend>)>,
viewport_dimensions: ViewportDimensions,
) -> Result<String> {
let base_path = Path::new(&test.output_path).parent().unwrap();
let mut executor = NullExecutor::new();
let mut frame_time = 1000.0 / movie.frame_rate().to_f64();
if let Some(tr) = test.options.tick_rate {
@ -46,7 +47,7 @@ pub fn run_swf(
let log = TestLogBackend::default();
let (fs_command_provider, fs_commands) = TestFsCommandProvider::new();
let navigator = TestNavigatorBackend::new(
base_path,
test.root_path.clone(),
&executor,
socket_events,
test.options.log_fetch.then(|| log.clone()),
@ -87,7 +88,7 @@ pub fn run_swf(
if test.options.num_frames.is_none() && test.options.num_ticks.is_none() {
return Err(anyhow!(
"Test {} must specify at least one of num_frames or num_ticks",
test.swf_path.to_string_lossy()
&test.name
));
}
@ -144,7 +145,7 @@ pub fn run_swf(
return Err(anyhow!("Encountered fscommand to capture and compare image '{name}', but the trigger was expected to be {:?}", image_comparison.trigger));
}
capture_and_compare_image(
base_path,
&test.root_path,
&player,
&name,
image_comparison,
@ -216,7 +217,7 @@ pub fn run_swf(
.remove(&name)
.expect("Name was just retrieved from map, should not be missing!");
capture_and_compare_image(
base_path,
&test.root_path,
&player,
&name,
image_comparison,
@ -236,7 +237,7 @@ pub fn run_swf(
.expect("Name was just retrieved from map, should not be missing!");
capture_and_compare_image(
base_path,
&test.root_path,
&player,
&name,
image_comparison,
@ -265,7 +266,7 @@ pub fn run_swf(
}
fn capture_and_compare_image(
base_path: &Path,
base_path: &VfsPath,
player: &Arc<Mutex<Player>>,
name: &String,
image_comparison: ImageComparison,
@ -280,9 +281,9 @@ fn capture_and_compare_image(
let actual_image = render_interface.capture(player_lock.renderer_mut());
let expected_image_path = base_path.join(format!("{name}.expected.png"));
if expected_image_path.is_file() {
let expected_image = image::open(&expected_image_path)
let expected_image_path = base_path.join(format!("{name}.expected.png"))?;
if expected_image_path.is_file()? {
let expected_image = image::load_from_memory(&read_bytes(&expected_image_path)?)
.context("Failed to open expected image")?
.into_rgba8();
@ -296,7 +297,7 @@ fn capture_and_compare_image(
)?;
} else if !known_failure {
// If we're expecting this to be wrong, don't save a likely wrong image
actual_image.save(expected_image_path)?;
write_image(&expected_image_path, &actual_image, ImageOutputFormat::Png)?;
}
} else if known_failure {
// It's possible that the trace output matched but the image might not.

View File

@ -2,30 +2,32 @@ use crate::environment::Environment;
use crate::options::TestOptions;
use crate::runner::run_swf;
use crate::set_logger;
use anyhow::{anyhow, Context, Result};
use crate::util::read_bytes;
use anyhow::{anyhow, Result};
use pretty_assertions::Comparison;
use ruffle_core::tag_utils::SwfMovie;
use ruffle_core::Player;
use ruffle_input_format::InputInjector;
use ruffle_socket_format::SocketEvent;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use vfs::VfsPath;
pub struct Test {
pub options: TestOptions,
pub swf_path: PathBuf,
pub input_path: PathBuf,
pub socket_path: PathBuf,
pub output_path: PathBuf,
pub swf_path: VfsPath,
pub input_path: VfsPath,
pub socket_path: VfsPath,
pub output_path: VfsPath,
pub root_path: VfsPath,
pub name: String,
}
impl Test {
pub fn from_options(options: TestOptions, test_dir: &Path, name: String) -> Result<Self> {
let swf_path = test_dir.join("test.swf");
let input_path = test_dir.join("input.json");
let socket_path = test_dir.join("socket.json");
let output_path = options.output_path(test_dir);
pub fn from_options(options: TestOptions, test_dir: VfsPath, name: String) -> Result<Self> {
let swf_path = test_dir.join("test.swf")?;
let input_path = test_dir.join("input.json")?;
let socket_path = test_dir.join("socket.json")?;
let output_path = options.output_path(&test_dir)?;
Ok(Self {
options,
@ -33,20 +35,11 @@ impl Test {
input_path,
socket_path,
output_path,
root_path: test_dir,
name,
})
}
pub fn from_options_file(options_path: &Path, name: String) -> Result<Self> {
Self::from_options(
TestOptions::read(options_path).context("Couldn't load test options")?,
options_path
.parent()
.context("Couldn't get test directory")?,
name,
)
}
pub fn run(
&self,
mut before_start: impl FnMut(Arc<Mutex<Player>>) -> Result<()>,
@ -54,8 +47,10 @@ impl Test {
environment: &impl Environment,
) -> Result<()> {
set_logger();
let movie =
SwfMovie::from_path(&self.swf_path, None).map_err(|e| anyhow!(e.to_string()))?;
let data = read_bytes(&self.swf_path)?;
let movie = SwfMovie::from_data(&data, format!("file:///{}", self.swf_path.as_str()), None)
.map_err(|e| anyhow!(e.to_string()))?;
let viewport_dimensions = self.options.player_options.viewport_dimensions(&movie);
let renderers = self
.options
@ -94,16 +89,18 @@ impl Test {
}
fn socket_events(&self) -> Result<Option<Vec<SocketEvent>>> {
Ok(if self.socket_path.is_file() {
Some(SocketEvent::from_file(&self.socket_path)?)
Ok(if self.socket_path.is_file()? {
Some(SocketEvent::from_reader(
&read_bytes(&self.socket_path)?[..],
)?)
} else {
None
})
}
fn input_injector(&self) -> Result<InputInjector> {
Ok(if self.input_path.is_file() {
InputInjector::from_file(&self.input_path)?
Ok(if self.input_path.is_file()? {
InputInjector::from_reader(&read_bytes(&self.input_path)?[..])?
} else {
InputInjector::empty()
})
@ -121,7 +118,7 @@ impl Test {
}
pub fn compare_output(&self, actual_output: &str) -> Result<()> {
let expected_output = std::fs::read_to_string(&self.output_path)?.replace("\r\n", "\n");
let expected_output = self.output_path.read_to_string()?.replace("\r\n", "\n");
if let Some(approximations) = &self.options.approximations {
if actual_output.lines().count() != expected_output.lines().count() {

View File

@ -0,0 +1,31 @@
use image::{EncodableLayout, ImageBuffer, ImageOutputFormat, Pixel, PixelWithColorType};
use std::io::{Cursor, Read, Write};
use std::ops::Deref;
use vfs::{VfsError, VfsPath};
pub fn read_bytes(path: &VfsPath) -> Result<Vec<u8>, VfsError> {
let mut bytes = Vec::with_capacity(path.metadata()?.len as usize);
path.open_file()?.read_to_end(&mut bytes)?;
Ok(bytes)
}
pub fn write_bytes(path: &VfsPath, data: &[u8]) -> Result<(), VfsError> {
path.create_file()?.write_all(data)?;
Ok(())
}
pub fn write_image<P, Container>(
path: &VfsPath,
image: &ImageBuffer<P, Container>,
format: ImageOutputFormat,
) -> anyhow::Result<()>
where
P: Pixel + PixelWithColorType,
[P::Subpixel]: EncodableLayout,
Container: Deref<Target = [P::Subpixel]>,
{
let mut buffer = vec![];
image.write_to(&mut Cursor::new(&mut buffer), format)?;
write_bytes(path, &buffer)?;
Ok(())
}

View File

@ -57,6 +57,19 @@ impl InputInjector {
})
}
/// Construct an input injector from an input reader and a platform-specific
/// event sink.
pub fn from_reader<R>(reader: R) -> Result<Self, io::Error>
where
R: io::Read,
{
Ok(Self {
items: from_reader(reader)?,
pos: 0,
buttons: MouseButtons::empty(),
})
}
/// Create an empty input injector with no input to inject.
///
/// Useful to represent a missing input file in cases where providing one

View File

@ -25,4 +25,11 @@ impl SocketEvent {
Ok(from_reader(file)?)
}
pub fn from_reader<R>(reader: R) -> Result<Vec<Self>, io::Error>
where
R: io::Read,
{
Ok(from_reader(reader)?)
}
}

View File

@ -4,8 +4,8 @@ use ruffle_test_framework::environment::Environment;
use ruffle_test_framework::options::TestOptions;
use ruffle_test_framework::set_logger;
use ruffle_test_framework::test::Test;
use ruffle_test_framework::vfs::{PhysicalFS, VfsPath};
use std::collections::BTreeMap;
use std::path::Path;
pub fn external_interface_avm1(
environment: &impl Environment,
@ -16,7 +16,7 @@ pub fn external_interface_avm1(
num_frames: Some(1),
..Default::default()
},
Path::new("tests/swfs/avm1/external_interface/"),
VfsPath::new(PhysicalFS::new("tests/swfs/avm1/external_interface/")),
"external_interface_avm1".to_string(),
)?
.run(
@ -75,7 +75,7 @@ pub fn external_interface_avm2(
num_frames: Some(1),
..Default::default()
},
Path::new("tests/swfs/avm2/external_interface/"),
VfsPath::new(PhysicalFS::new("tests/swfs/avm2/external_interface/")),
"external_interface_avm2".to_string(),
)?
.run(

View File

@ -8,8 +8,10 @@ use crate::shared_object::{shared_object_avm1, shared_object_avm2, shared_object
use anyhow::Context;
use anyhow::Result;
use libtest_mimic::{Arguments, Trial};
use ruffle_test_framework::options::TestOptions;
use ruffle_test_framework::test::Test;
use std::panic::{catch_unwind, resume_unwind};
use ruffle_test_framework::vfs::{PhysicalFS, VfsPath};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
use std::path::Path;
mod environment;
@ -54,11 +56,17 @@ fn main() {
.to_string_lossy()
.replace('\\', "/");
if is_candidate(&args, &name) {
let test = Test::from_options_file(file.path(), name.clone())
let root = VfsPath::new(PhysicalFS::new(file.path().parent().unwrap() ));
let test = Test::from_options(
TestOptions::read(&root.join("test.toml").unwrap()).context("Couldn't load test options").unwrap(),
root,
name.clone(),
)
.with_context(|| format!("Couldn't create test {name}"))
.unwrap();
let ignore = !test.should_run(!args.list, &NativeEnvironment);
let mut trial = Trial::test(test.name.to_string(), move || {
let test = AssertUnwindSafe(test);
let unwind_result = catch_unwind(|| test.run(|_| Ok(()), |_| Ok(()), &NativeEnvironment));
if test.options.known_failure {
match unwind_result {

View File

@ -3,7 +3,7 @@ use ruffle_test_framework::environment::Environment;
use ruffle_test_framework::options::TestOptions;
use ruffle_test_framework::set_logger;
use ruffle_test_framework::test::Test;
use std::path::Path;
use ruffle_test_framework::vfs::{PhysicalFS, VfsPath};
pub fn shared_object_avm1(environment: &impl Environment) -> Result<(), libtest_mimic::Failed> {
set_logger();
@ -19,7 +19,7 @@ pub fn shared_object_avm1(environment: &impl Environment) -> Result<(), libtest_
output_path: "output1.txt".into(),
..Default::default()
},
Path::new("tests/swfs/avm1/shared_object/"),
VfsPath::new(PhysicalFS::new("tests/swfs/avm1/shared_object/")),
"shared_object_avm1".to_string(),
)?
.run(
@ -49,7 +49,7 @@ pub fn shared_object_avm1(environment: &impl Environment) -> Result<(), libtest_
output_path: "output2.txt".into(),
..Default::default()
},
Path::new("tests/swfs/avm1/shared_object/"),
VfsPath::new(PhysicalFS::new("tests/swfs/avm1/shared_object/")),
"shared_object_avm1".to_string(),
)?
.run(
@ -82,7 +82,7 @@ pub fn shared_object_self_ref_avm1(
output_path: "output1.txt".into(),
..Default::default()
},
Path::new("tests/swfs/avm1/shared_object_self_ref/"),
VfsPath::new(PhysicalFS::new("tests/swfs/avm1/shared_object_self_ref/")),
"shared_object_self_ref_avm1".to_string(),
)?
.run(
@ -112,7 +112,7 @@ pub fn shared_object_self_ref_avm1(
output_path: "output2.txt".into(),
..Default::default()
},
Path::new("tests/swfs/avm1/shared_object_self_ref/"),
VfsPath::new(PhysicalFS::new("tests/swfs/avm1/shared_object_self_ref/")),
"shared_object_self_ref_avm1".to_string(),
)?
.run(
@ -143,7 +143,7 @@ pub fn shared_object_avm2(environment: &impl Environment) -> Result<(), libtest_
output_path: "output1.txt".into(),
..Default::default()
},
Path::new("tests/swfs/avm2/shared_object/"),
VfsPath::new(PhysicalFS::new("tests/swfs/avm2/shared_object/")),
"shared_object_avm2".to_string(),
)?
.run(
@ -173,7 +173,7 @@ pub fn shared_object_avm2(environment: &impl Environment) -> Result<(), libtest_
output_path: "output2.txt".into(),
..Default::default()
},
Path::new("tests/swfs/avm2/shared_object/"),
VfsPath::new(PhysicalFS::new("tests/swfs/avm2/shared_object/")),
"shared_object_avm2".to_string(),
)?
.run(

View File

@ -44,7 +44,7 @@ transform.matrix = (a=1, b=0, c=0, d=1, tx=0, ty=0)
transform.concatenatedMatrix = (a=1, b=0, c=0, d=1, tx=0, ty=0)
transform.pixelBounds = (x=0, y=0, w=0, h=0)
useHandCursor = true
_url = movieclip_default_state/test.swf
_url = /test.swf
getBounds(this).xMin = 6710886.4
getBounds(this).xMax = 6710886.4
getBounds(this).yMin = 6710886.4

View File

@ -1,26 +1,552 @@
// SWF Version 10
/*
* This test tests the default state of a MovieClip.
* The default state consists of the values of all properties and the results of some getter functions
* after the MovieClip has been created with createEmptyMovieClip.
* This test tests the different states of a MovieClip.
* A state of a MovieClip consists of the values of all properties and the results of some getter
* functions of the MovieClip.
*
* The other MovieClip states are tested in movieclip_state_values.
* The states a MovieClip can be in are the following:
* - Default State
* This is the default state that a MovieClip is in after it's created with createEmptyMovieClip.
* It is the only one not tested in this test. Instead, it gets tested in movieclip_default_state.
* - Initial Loading State
* This state is entered when FP / Ruffle try to load the MovieClip. As soon as FP / Ruffle
* either load the first frame of the SWF or realise the movie can't be loaded, a different state
* is entered.
* Therefore, if FP / Ruffle are too fast to determine whether the file exists or not, the state
* can directly change after one frame from the default state to a different state.
* The initial loading state is different, depending on whether the SWF file which is loading is an
* online file or a local file.
* The local initial loading state is tested in this test.
* - Error state
* This state is entered if no file could be loaded or if the loaded content is no valid supported
* content.
* It is tested in this test.
* - Image state
* This state is entered if an image has been loaded.
* It is tested in this test.
* - Success state
* This state is entered if the first frame of a valid SWF file has been loaded.
* It is tested in this test.
* - Unloaded state
* This state is entered on the next frame after the movie has been unloaded.
* It is tested in this test.
*
* This test consists of several tests which each having a MovieClip go through these states and testing
* if the values are correct.
* More information about the details of each test is given as documentation of the function executing
* the respective test.
*
* This test currently only tests the MovieClip states when trying to load a local file.
* It still contains the test cases for remote URLs, however, they are not executed since Ruffle tests
* currently don't support tests using remote URLs.
* If Ruffle tests start supporting remote URLs (e.g. with a local server) at some point, these tests
* can still be used.
*/
var mc = createEmptyMovieClip("testMovieClip", getNextHighestDepth());
printMcProps(getMcPropsArray(mc));
startNextTest(0);
/*
* This function is called by each test and calls the next one.
*/
function startNextTest(currentTest:Number) {
switch (currentTest) {
case 0: testOfflineErrorCase(1); break;
case 1: testOfflineErrorCase(2); break
case 2: testOfflineSuccessCase(3); break;
case 3: testOfflineSuccessCase(4); break
case 4: trace("\nFinished all tests."); break;
default: trace("\nError: Test number " + currentTest + " does not exist."); break;
}
}
/*
* This traces all elements of an McProps-Array returned by getMcPropsArray with their respective names.
* Flash behaves the same if a file doesn't exist or isn't a valid file, so this tests both.
* In this test (in FP), the MovieClip has four states:
* The first (default) state is entered when creating an empty movie clip => _framesloaded == 1
* The second (initial loading) state is entered when FP tries to load the MovieClip => _framesloaded == -1
* The third (error) state is entered when FP realises the movie can't be loaded => _framesloaded == -1
* The fourth (unloaded) state is entered on the next frame after the movie has been unloaded.
* Ruffle is too fast to determine whether the file exists or not to (reliably) still be in the initial loading
* state after one frame (Flash player often is too fast as well).
*/
function printMcProps(mcPropsArray:Array) {
for (var propIterator = 0; propIterator < mcPropsArray.length; propIterator++) {
var propName = mcPropsArray[propIterator][0];
var propValue = mcPropsArray[propIterator][1];
trace(propName + " = " + propValue);
function testOfflineErrorCase(currentTest:Number) {
var mc: MovieClip;
var mcPropsArray1: Array;
var mcPropsArray2: Array;
var mcPropsArray3: Array;
var mcPropsArray4: Array;
var mcPropsArray5: Array;
var mcPropsArray6: Array;
switch (currentTest) {
case 1:
trace("Test 1 (Offline; Link doesn't exist)");
mc = createEmptyMovieClip("testMovieClip1", getNextHighestDepth());
mcPropsArray1 = getMcPropsArray(mc);
loadMovie("no existing file.swf", mc);
break;
case 2:
trace("\nTest 2 (Offline; Link is a text file)");
mc = createEmptyMovieClip("testMovieClip2", getNextHighestDepth());
mcPropsArray1 = getMcPropsArray(mc);
loadMovie("no correct file (text).swf", mc);
break;
default:
trace("Error: Test number " + currentTest + " is invalid in this context.");
return;
}
trace("loadMovie command issued.");
mcPropsArray2 = getMcPropsArray(mc);
compareMcProps(mcPropsArray1, mcPropsArray2, true);
var frameCount = 0;
var initialOneFinished = false;
var errorCount = 0;
var unloaded = false;
this.onEnterFrame = function() {
frameCount++;
if (!unloaded) {
if (mc._framesloaded == 1) {
// Sometimes in Flash, the MovieClip is still in the first state for one frame.
if (!initialOneFinished) {
var changes = compareMcProps(mcPropsArray2, getMcPropsArray(mc), false);
if (changes.length != 0) {
trace("Error: _framesloaded must be -1.")
}
initialOneFinished = true;
} else {
trace("Error: _framesloaded must be -1.");
}
} else if (mc._framesloaded == -1) {
// Flash uses _framesloaded == -1 both in the initial loading and in the error state for
// offline files (which are identical).
// Therefore, we wait 10 frames to be sure we're in the error state.
errorCount++;
if (errorCount == 1) {
// This might be the initial loading state or (if Ruffle was too fast) the error state.
trace("Frames loaded: -1 (1st time)");
mcPropsArray3 = getMcPropsArray(mc);
compareMcProps(mcPropsArray2, mcPropsArray3, true);
} else if (errorCount >= 10) {
// This is the error state. No matter which state was previously entered, nothing should
// have changed.
trace ("Frames loaded: -1 (10th time)")
mcPropsArray4 = getMcPropsArray(mc);
compareMcProps(mcPropsArray3, mcPropsArray4, true);
// The MovieClip is unloaded and will enter the unloaded state on the next frame.
unloadMovie(mc);
unloaded = true;
trace("unloadMovie command issued.")
mcPropsArray5 = getMcPropsArray(mc);
compareMcProps(mcPropsArray4, mcPropsArray5, true);
}
} else {
trace("Error: _framesloaded must be -1.");
}
} else {
mcPropsArray6 = getMcPropsArray(mc);
compareMcProps(mcPropsArray5, mcPropsArray6, true);
this.onEnterFrame = null;
startNextTest(currentTest);
return;
}
if (frameCount >= 15) {
trace("Error: No error has occurred.");
this.onEnterFrame = null;
startNextTest(currentTest);
}
};
}
/*
* Flash behaves mostly the same if a file is an image or a valid SWF file, so this tests both.
* In this test (in FP), the MovieClip has four states:
* The first (default) state is entered when creating an empty movie clip => _framesloaded == 1
* The second (initial loading) state is entered when FP tries to load the MovieClip => _framesloaded == -1
* The third (success / image) state is entered after FP loaded the first frame => _framesloaded == 1
* (With real SWF files, _framesloaded doesn't go to one but jumps from 0 to a number and rises until
* the full file is loaded, but an image only counts as one frame and the target.swf file only has one frame.)
* The fourth (unloaded) state is entered on the next frame after the movie has been unloaded.
* Ruffle is too fast to determine whether the file exists or not to (reliably) still be in the initial loading
* state after one frame (Flash player often is too fast as well).
*/
function testOfflineSuccessCase(currentTest:Number) {
var mc: MovieClip;
var mcPropsArray1: Array;
var mcPropsArray2: Array;
var mcPropsArray3: Array;
var mcPropsArray4: Array;
var mcPropsArray5: Array;
switch (currentTest) {
case 3:
trace("\nTest 3 (Offline; Link is an image)");
mc = createEmptyMovieClip("testMovieClip3", getNextHighestDepth());
mcPropsArray1 = getMcPropsArray(mc);
loadMovie("no correct file (image).swf", mc);
break;
case 4:
trace("\nTest 4 (Offline; Link is a valid file)");
mc = createEmptyMovieClip("testMovieClip4", getNextHighestDepth());
mcPropsArray1 = getMcPropsArray(mc);
loadMovie("target.swf", mc);
break;
default:
trace("Error: Test number " + currentTest + " is invalid in this context.");
return;
}
trace("loadMovie command issued.");
mcPropsArray2 = getMcPropsArray(mc);
compareMcProps(mcPropsArray1, mcPropsArray2, true);
var frameCount = 0;
var initialOneFinished = false;
var loadingStateStarted = false;
var unloaded = false;
this.onEnterFrame = function() {
frameCount++;
if (!unloaded) {
if (mc._framesloaded == 1 && !initialOneFinished) {
// Sometimes in Flash, the MovieClip is still in the first state for one frame.
var changes = compareMcProps(mcPropsArray2, getMcPropsArray(mc), false);
if (changes.length != 0) {
// It's not the first state => We test if it's the success state.
initialOneFinished = true;
}
} else if (mc._framesloaded == -1) {
// Ruffle is (almost always) too fast to determine whether the file exists or not (and Flash often
// is as well); therefore this won't trace anything if the state is correct.
if (!loadingStateStarted) {
var mcPropsArrayLoadingState = getMcPropsArray(mc);
var differenceArray = compareMcProps(mcPropsArray2, mcPropsArrayLoadingState, false);
var correctDifferenceArray = [["_framesloaded", 1, -1], ["_totalframes", 1, 0],
["_url", "movieclip_state_values/test.swf", "movieclip_state_values/target.swf"],
["getBytesTotal()", 0, -1], ["getSWFVersion()", 10, -1]];
compareArrays(differenceArray, correctDifferenceArray, 2);
loadingStateStarted = true;
}
} else if (mc._framesloaded != 1) {
trace("Error: _framesloaded must be -1 or 1.");
}
if (mc._framesloaded == 1 && initialOneFinished) {
trace("Frames loaded: 1");
mcPropsArray3 = getMcPropsArray(mc);
compareMcProps(mcPropsArray2, mcPropsArray3, true);
// The MovieClip is unloaded and will enter the unloaded state on the next frame.
unloadMovie(mc);
unloaded = true;
trace("unloadMovie command issued.")
mcPropsArray4 = getMcPropsArray(mc);
compareMcProps(mcPropsArray3, mcPropsArray4, true);
}
initialOneFinished = true;
} else {
mcPropsArray5 = getMcPropsArray(mc);
compareMcProps(mcPropsArray4, mcPropsArray5, true);
this.onEnterFrame = null;
startNextTest(currentTest);
return;
}
if (frameCount >= 10) {
trace("Error: The MovieClip has not loaded.");
this.onEnterFrame = null;
startNextTest(currentTest);
}
};
}
/*
* The following code is unused since Ruffle tests currently don't support tests using remote URLs.
* It is kept here because Flash behaves differently with online SWFs and offline SWFs.
* If Ruffle tests start supporting remote URLs (e.g. with a local server) at some point, these tests
* can still be used.
*/
// TODO: Replace these with the valid domain / URL to the folder with the files.
var base_domain = "";
var base_url = "";
/*
* Flash behaves the same if a file doesn't exist or isn't a valid file, so this tests both.
* In this test (In FP), the MovieClip has four states:
* The first (default) state is entered when creating an empty movie clip => _framesloaded == 1
* The second (initial loading) state is entered when FP tries to load the MovieClip => _framesloaded == 0
* The third (error) state is entered when FP realises the movie can't be loaded => _framesloaded == -1
* The fourth (unloaded) state is entered on the next frame after the movie has been unloaded.
* Flash often is too fast to determine whether the file exists or not to still be in the initial loading
* state after one frame (Ruffle sometimes is too fast as well).
*/
function testOnlineErrorCase(currentTest:Number) {
var mc: MovieClip
var mcPropsArray1: Array;
var mcPropsArray2: Array;
var mcPropsArray3: Array;
var mcPropsArray4: Array;
var mcPropsArray5: Array;
System.security.allowDomain(base_domain);
switch (currentTest) {
case 5:
trace("Test 5 (Online; Link doesn't exist)");
mc = createEmptyMovieClip("testMovieClip5", getNextHighestDepth());
mcPropsArray1 = getMcPropsArray(mc);
loadMovie(base_url + "/no existing file.swf", mc);
break;
case 6:
trace("\nTest 6 (Online; Link is a text file)");
mc = createEmptyMovieClip("testMovieClip6", getNextHighestDepth());
mcPropsArray1 = getMcPropsArray(mc);
loadMovie(base_url + "/no correct file (text).swf", mc);
break;
default:
trace("Error: Test number " + currentTest + " invalid in this context.");
return;
}
trace("loadMovie command issued.");
mcPropsArray2 = getMcPropsArray(mc);
compareMcProps(mcPropsArray1, mcPropsArray2, true);
var frameCount = 0;
var initialOneFinished = false;
var loadingStateStarted = false;
var unloaded = false;
this.onEnterFrame = function() {
frameCount++;
if (!unloaded) {
if (mc._framesloaded == 1) {
// Sometimes in Flash, the MovieClip is still in the first state for one frame.
if (!initialOneFinished) {
var changes = compareMcProps(mcPropsArray2, getMcPropsArray(mc), false);
if (changes.length != 0) {
trace("Error: _framesloaded must be between -1 and 0.");
}
initialOneFinished = true;
} else {
trace("Error: _framesloaded must be between -1 and 0.");
}
} else if (mc._framesloaded == 0) {
// Flash often is too fast to determine whether the file exists or not (and Ruffle sometimes
// is as well); therefore this won't trace anything if the state is correct.
if (!loadingStateStarted) {
var mcPropsArrayLoadingState = getMcPropsArray(mc);
var differenceArray = compareMcProps(mcPropsArray2, mcPropsArrayLoadingState, false);
var correctDifferenceArray = [["_framesloaded", 1, 0], ["_totalframes", 1, 0]];
compareArrays(differenceArray, correctDifferenceArray, 2);
loadingStateStarted = true;
}
} else if (mc._framesloaded == -1) {
trace("Frames loaded: -1");
mcPropsArray3 = getMcPropsArray(mc);
compareMcProps(mcPropsArray2, mcPropsArray3, true);
// The MovieClip is unloaded and will enter the unloaded state on the next frame.
unloadMovie(mc);
unloaded = true;
trace("unloadMovie command issued.")
mcPropsArray5 = getMcPropsArray(mc);
compareMcProps(mcPropsArray3, mcPropsArray4, true);
} else {
trace("Error: _framesloaded must be between -1 and 0.");
}
} else {
mcPropsArray5 = getMcPropsArray(mc);
compareMcProps(mcPropsArray4, mcPropsArray5, true);
this.onEnterFrame = null;
startNextTest(currentTest);
return;
}
if (frameCount >= 10) {
trace("Error: No error has occurred.");
this.onEnterFrame = null;
startNextTest(currentTest);
}
};
}
/*
* Flash behaves mostly the same if a file is an image or a valid SWF file, so this tests both.
* In this test (in FP), the MovieClip has three states:
* The first (default) state is entered when creating an empty movie clip => _framesloaded == 1
* The second (initial loading) state is entered when FP tries to load the MovieClip => _framesloaded == 0
* The third (success) state is entered after FP loaded the first frame => _framesloaded == 1
* (With real SWF files, _framesloaded doesn't go to one but jumps from 0 to a number and rises until
* the full file is loaded, but an image only counts as one frame and the target.swf file only has one frame.)
* Flash often is too fast to determine whether the file exists or not to still be in the initial loading
* state after one frame (Ruffle sometimes is too fast as well).
*/
function testOnlineSuccessCase(currentTest:Number) {
var mc: MovieClip;
var mcPropsArray1: Array;
var mcPropsArray2: Array;
var mcPropsArray3: Array;
var mcPropsArray4: Array;
var mcPropsArray5: Array;
System.security.allowDomain(base_domain);
switch (currentTest) {
case 7:
trace("\nTest 7 (Online; Link is an image)");
mc = createEmptyMovieClip("testMovieClip7", getNextHighestDepth());
mcPropsArray1 = getMcPropsArray(mc);
loadMovie(base_url + "/no correct file (image).swf", mc);
break;
case 8:
trace("\nTest 8 (Online; Link is a valid file)");
mc = createEmptyMovieClip("testMovieClip8", getNextHighestDepth());
mcPropsArray1 = getMcPropsArray(mc);
loadMovie(base_url + "/target.swf", mc);
break;
default:
trace("Error: Test number " + currentTest + " invalid in this context.");
return;
}
trace("loadMovie command issued.");
mcPropsArray2 = getMcPropsArray(mc);
compareMcProps(mcPropsArray1, mcPropsArray2, true);
var frameCount = 0;
var initialOneFinished = false;
var loadingStateStarted = false;
var unloaded = false;
this.onEnterFrame = function() {
frameCount++;
if (!unloaded) {
if (mc._framesloaded == 1 && !initialOneFinished) {
// Sometimes in Flash, the MovieClip is still in the first state for one frame.
var changes = compareMcProps(mcPropsArray2, getMcPropsArray(mc), false);
if (changes.length != 0) {
// It's not the first state => We test if it's the success state.
initialOneFinished = true;
}
} else if (mc._framesloaded == 0) {
// Flash often is too fast to determine whether the file exists or not (and Ruffle sometimes
// is as well); therefore this won't trace anything if the state is correct.
if (!loadingStateStarted) {
var mcPropsArrayLoadingState = getMcPropsArray(mc);
var differenceArray = compareMcProps(mcPropsArray2, mcPropsArrayLoadingState, false);
var correctDifferenceArray = [["_framesloaded", 1, 0], ["_totalframes", 1, 0]];
compareArrays(differenceArray, correctDifferenceArray, 2);
loadingStateStarted = true;
}
} else if (mc._framesloaded != 1) {
trace("Error: _framesloaded must be between 0 and 1.");
}
if (mc._framesloaded == 1 && initialOneFinished) {
trace("Frames loaded: 1");
mcPropsArray3 = getMcPropsArray(mc);
compareMcProps(mcPropsArray2, mcPropsArray3, true);
// The MovieClip is unloaded and will enter the unloaded state on the next frame.
unloadMovie(mc);
unloaded = true;
trace("unloadMovie command issued.")
mcPropsArray4 = getMcPropsArray(mc);
compareMcProps(mcPropsArray3, mcPropsArray4, true);
}
initialOneFinished = true;
} else {
mcPropsArray5 = getMcPropsArray(mc);
compareMcProps(mcPropsArray4, mcPropsArray5, true);
this.onEnterFrame = null;
startNextTest(currentTest);
return;
}
if (frameCount >= 10) {
trace("Error: The MovieClip has not loaded.");
this.onEnterFrame = null;
startNextTest(currentTest);
}
};
}
/*
* This compares different arrays with a fixed dimension and traces an error if they are different.
*/
function compareArrays(array1:Array, array2:Array, arrayDimension:Number) {
if (array1.length != array2.length) {
trace("Error: The array lengths are not equal.")
return;
}
for (var elementIterator = 0; elementIterator < array1.length; elementIterator++) {
if (arrayDimension == 1) {
if (array1[elementIterator] != array2[elementIterator]) {
trace("Error: The arrays are not equal.");
return;
}
} else {
compareArrays(array1[elementIterator], array2[elementIterator], arrayDimension - 1);
}
}
}
/*
* This compares two McProps-Arrays returned by getMcPropsArray.
* If traceDifferences is true, all differences will be traced.
* It returns the diverging values with their respective names in a two-dimensional array.
*/
function compareMcProps(mcPropsArray1:Array, mcPropsArray2:Array, traceDifferences:Boolean) {
if (mcPropsArray1 == undefined || mcPropsArray2 == undefined) {
trace("Error: An mcPropsArray is undefined.");
return;
}
var differenceArray = [];
for (var propIterator = 0; propIterator < mcPropsArray1.length; propIterator++) {
if (mcPropsArray1[propIterator][1].toString() != mcPropsArray2[propIterator][1].toString()) {
var value1 = mcPropsArray1[propIterator][1];
var value2 = mcPropsArray2[propIterator][1];
var propName = mcPropsArray1[propIterator][0];
differenceArray.push([propName, value1, value2])
if (traceDifferences) {
trace("Change: Prop " + propName + " is \"" + value1 + "\" on the first, but \"" + value2 +
"\" on the second target.");
}
}
}
if (traceDifferences && differenceArray.length == 0) {
trace("Both targets have the same props.");
}
return differenceArray;
}
@ -77,9 +603,7 @@ function getMcPropsArray(mc:MovieClip) {
mcProps.push(["getRect(mc).yMin", getRectMc.yMin]);
mcProps.push(["getRect(mc).yMax", getRectMc.yMax]);
mcProps.push(["getSWFVersion()", mc.getSWFVersion()]);
// mcProps.push(["getTextSnapshot().getCount()", mc.getTextSnapshot().getCount()]);
// TODO: Re-add getTextSnapshot().getCount() to this test after it has been implemented / stubbed for AVM1.
mcProps.push(["getTextSnapshot().getCount()", mc.getTextSnapshot().getCount()]);
return mcProps;
}

View File

@ -1,4 +1,4 @@
ruffle.getBytesTotal(): 2685
ruffle.getBytesLoaded(): 2685
ruffle.getSWFVersion(): -1
ruffle._url (unescaped & split): movieclip_methods_with_loaded_image/target.png
ruffle._url (unescaped & split): target.png

View File

@ -8,7 +8,7 @@ _root.onEnterFrame = function() {
trace("ruffle.getBytesLoaded(): " + bytesLoaded);
trace("ruffle.getSWFVersion(): " + ruffle.getSWFVersion());
_root.url = unescape(ruffle._url).split("/");
url = url[url.length - 2] + "/" + url[url.length - 1];
url = url[url.length - 1];
trace("ruffle._url (unescaped & split): " + url);
delete onEnterFrame;
}

View File

@ -4,7 +4,7 @@ Both targets have the same props.
Frames loaded: -1 (1st time)
Change: Prop _framesloaded is "1" on the first, but "-1" on the second target.
Change: Prop _totalframes is "1" on the first, but "0" on the second target.
Change: Prop _url is "movieclip_state_values/test.swf" on the first, but "movieclip_state_values/no existing file.swf" on the second target.
Change: Prop _url is "test.swf" on the first, but "no existing file.swf" on the second target.
Change: Prop getBytesTotal() is "0" on the first, but "-1" on the second target.
Change: Prop getSWFVersion() is "10" on the first, but "-1" on the second target.
Frames loaded: -1 (10th time)
@ -12,7 +12,7 @@ Both targets have the same props.
unloadMovie command issued.
Both targets have the same props.
Change: Prop _framesloaded is "-1" on the first, but "0" on the second target.
Change: Prop _url is "movieclip_state_values/no existing file.swf" on the first, but "movieclip_state_values/test.swf" on the second target.
Change: Prop _url is "no existing file.swf" on the first, but "test.swf" on the second target.
Change: Prop getBytesTotal() is "-1" on the first, but "0" on the second target.
Change: Prop getSWFVersion() is "-1" on the first, but "10" on the second target.
@ -22,7 +22,7 @@ Both targets have the same props.
Frames loaded: -1 (1st time)
Change: Prop _framesloaded is "1" on the first, but "-1" on the second target.
Change: Prop _totalframes is "1" on the first, but "0" on the second target.
Change: Prop _url is "movieclip_state_values/test.swf" on the first, but "movieclip_state_values/no correct file (text).swf" on the second target.
Change: Prop _url is "test.swf" on the first, but "no correct file (text).swf" on the second target.
Change: Prop getBytesTotal() is "0" on the first, but "-1" on the second target.
Change: Prop getSWFVersion() is "10" on the first, but "-1" on the second target.
Frames loaded: -1 (10th time)
@ -30,7 +30,7 @@ Both targets have the same props.
unloadMovie command issued.
Both targets have the same props.
Change: Prop _framesloaded is "-1" on the first, but "0" on the second target.
Change: Prop _url is "movieclip_state_values/no correct file (text).swf" on the first, but "movieclip_state_values/test.swf" on the second target.
Change: Prop _url is "no correct file (text).swf" on the first, but "test.swf" on the second target.
Change: Prop getBytesTotal() is "-1" on the first, but "0" on the second target.
Change: Prop getSWFVersion() is "-1" on the first, but "10" on the second target.
@ -42,7 +42,7 @@ Change: Prop _currentframe is "0" on the first, but "1" on the second target.
Change: Prop _height is "0" on the first, but "985" on the second target.
Change: Prop _width is "0" on the first, but "1280" on the second target.
Change: Prop transform.pixelBounds is "(x=0, y=0, w=0, h=0)" on the first, but "(x=0, y=0, w=1280, h=985)" on the second target.
Change: Prop _url is "movieclip_state_values/test.swf" on the first, but "movieclip_state_values/no correct file (image).swf" on the second target.
Change: Prop _url is "test.swf" on the first, but "no correct file (image).swf" on the second target.
Change: Prop getBounds(this).xMin is "6710886.4" on the first, but "0" on the second target.
Change: Prop getBounds(this).xMax is "6710886.4" on the first, but "1280" on the second target.
Change: Prop getBounds(this).yMin is "6710886.4" on the first, but "0" on the second target.
@ -70,7 +70,7 @@ Change: Prop _height is "985" on the first, but "0" on the second target.
Change: Prop _totalframes is "1" on the first, but "0" on the second target.
Change: Prop _width is "1280" on the first, but "0" on the second target.
Change: Prop transform.pixelBounds is "(x=0, y=0, w=1280, h=985)" on the first, but "(x=0, y=0, w=0, h=0)" on the second target.
Change: Prop _url is "movieclip_state_values/no correct file (image).swf" on the first, but "movieclip_state_values/test.swf" on the second target.
Change: Prop _url is "no correct file (image).swf" on the first, but "test.swf" on the second target.
Change: Prop getBounds(this).xMin is "0" on the first, but "6710886.4" on the second target.
Change: Prop getBounds(this).xMax is "1280" on the first, but "6710886.4" on the second target.
Change: Prop getBounds(this).yMin is "0" on the first, but "6710886.4" on the second target.
@ -97,7 +97,7 @@ Both targets have the same props.
Child movie loaded!
Frames loaded: 1
Change: Prop _currentframe is "0" on the first, but "1" on the second target.
Change: Prop _url is "movieclip_state_values/test.swf" on the first, but "movieclip_state_values/target.swf" on the second target.
Change: Prop _url is "test.swf" on the first, but "target.swf" on the second target.
Change: Prop getBytesLoaded() is "0" on the first, but "68" on the second target.
Change: Prop getBytesTotal() is "0" on the first, but "68" on the second target.
Change: Prop getSWFVersion() is "10" on the first, but "8" on the second target.
@ -106,7 +106,7 @@ Both targets have the same props.
Change: Prop _currentframe is "1" on the first, but "0" on the second target.
Change: Prop _framesloaded is "1" on the first, but "0" on the second target.
Change: Prop _totalframes is "1" on the first, but "0" on the second target.
Change: Prop _url is "movieclip_state_values/target.swf" on the first, but "movieclip_state_values/test.swf" on the second target.
Change: Prop _url is "target.swf" on the first, but "test.swf" on the second target.
Change: Prop getBytesLoaded() is "68" on the first, but "0" on the second target.
Change: Prop getBytesTotal() is "68" on the first, but "0" on the second target.
Change: Prop getSWFVersion() is "8" on the first, but "10" on the second target.

View File

@ -572,7 +572,7 @@ function getMcPropsArray(mc:MovieClip) {
var url = unescape(mc._url);
if (url.indexOf("file:///") == 0) {
var urlSplit = url.split("/");
mcProps.push(["_url", urlSplit[urlSplit.length - 2] + "/" + urlSplit[urlSplit.length - 1]]);
mcProps.push(["_url", urlSplit[urlSplit.length - 1]]);
} else {
mcProps.push(["_url", url]);
}