From 911025423269349ff015fd08c76fd604cc6d18d2 Mon Sep 17 00:00:00 2001 From: SasakiSaki Date: Fri, 12 Jul 2019 06:13:49 +0800 Subject: [PATCH] Unit testing for Array and String (#63) * Make interpreter engine reusable * Testing Array.prototype.join() * Testing Array.prototype.concat() * Fix format problems * Testing String.prototype.length * Testing String.prototype.concat() * Try to fix String.prototype.length * Testing String.prototype.repeat() * Testing String.prototype.startsWith() * Testing String.prototype.endsWith() * Remove unnecessary `dbg!` --- .gitignore | 5 +++ src/lib/js/array.rs | 50 +++++++++++++++++++++ src/lib/js/string.rs | 104 ++++++++++++++++++++++++++++++++++++++++++- src/lib/lib.rs | 20 ++++++--- 4 files changed, 173 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 70ff6f225c..b7bb3b00d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# IDE +.idea/ +*.iml + +# Build target dist **/*.rs.bk diff --git a/src/lib/js/array.rs b/src/lib/js/array.rs index 4722f08509..c82183bbd8 100644 --- a/src/lib/js/array.rs +++ b/src/lib/js/array.rs @@ -267,7 +267,57 @@ pub fn _create(global: &Value) -> Value { array.set_field_slice(PROTOTYPE, proto); array } + /// Initialise the global object with the `Array` object pub fn init(global: &Value) { global.set_field_slice("Array", _create(global)); } + +#[cfg(test)] +mod tests { + use crate::exec::Executor; + use crate::forward; + + #[test] + fn concat() { + //TODO: array display formatter + let mut engine = Executor::new(); + let init = r#" + let empty = new Array(); + let one = new Array(1); + "#; + forward(&mut engine, init); + // Empty ++ Empty + let ee = forward(&mut engine, "empty.concat(empty)"); + //assert_eq!(ee, String::from("")); + // Empty ++ NonEmpty + let en = forward(&mut engine, "empty.concat(one)"); + //assert_eq!(en, String::from("a")); + // NonEmpty ++ Empty + let ne = forward(&mut engine, "one.concat(empty)"); + //assert_eq!(ne, String::from("a.b.c")); + // NonEmpty ++ NonEmpty + let nn = forward(&mut engine, "one.concat(one)"); + //assert_eq!(nn, String::from("a.b.c")); + } + + #[test] + fn join() { + let mut engine = Executor::new(); + let init = r#" + let empty = [ ]; + let one = ["a"]; + let many = ["a", "b", "c"]; + "#; + forward(&mut engine, init); + // Empty + let empty = forward(&mut engine, "empty.join('.')"); + assert_eq!(empty, String::from("")); + // One + let one = forward(&mut engine, "one.join('.')"); + assert_eq!(one, String::from("a")); + // Many + let many = forward(&mut engine, "many.join('.')"); + assert_eq!(many, String::from("a.b.c")); + } +} diff --git a/src/lib/js/string.rs b/src/lib/js/string.rs index ac5572857d..c203bd5a1d 100644 --- a/src/lib/js/string.rs +++ b/src/lib/js/string.rs @@ -25,7 +25,7 @@ pub fn make_string(this: Value, _: Value, args: Vec) -> ResultValue { /// Get a string's length pub fn get_string_length(this: Value, _: Value, _: Vec) -> ResultValue { let this_str: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap(); - Ok(to_value::(this_str.len() as i32)) + Ok(to_value::(this_str.chars().count() as i32)) } /// Get the string value to a primitive string @@ -484,6 +484,7 @@ pub fn _create(global: &Value) -> Value { string.set_field_slice(PROTOTYPE, proto); string } + /// Initialise the `String` object on the global object pub fn init(global: &Value) { global.set_field_slice("String", _create(global)); @@ -492,10 +493,111 @@ pub fn init(global: &Value) { #[cfg(test)] mod tests { use super::*; + use crate::exec::Executor; + use crate::forward; + #[test] fn check_string_constructor_is_function() { let global = ValueData::new_obj(None); let string_constructor = _create(&global); assert_eq!(string_constructor.is_function(), true); } + + #[test] + fn length() { + //TEST262: https://github.com/tc39/test262/blob/master/test/built-ins/String/length.js + let mut engine = Executor::new(); + let init = r#" + const a = new String(' '); + const b = new String('\ud834\udf06'); + const c = new String(' \b '); + cosnt d = new String('中文长度') + "#; + forward(&mut engine, init); + let a = forward(&mut engine, "a.length"); + assert_eq!(a, String::from("1")); + let b = forward(&mut engine, "b.length"); + // TODO: fix this + // unicode surrogate pair length should be 1 + // utf16/usc2 length should be 2 + // utf8 length should be 4 + //assert_eq!(b, String::from("2")); + let c = forward(&mut engine, "c.length"); + assert_eq!(c, String::from("3")); + let d = forward(&mut engine, "d.length"); + assert_eq!(d, String::from("4")); + } + + #[test] + fn concat() { + let mut engine = Executor::new(); + let init = r#" + const hello = new String('Hello, '); + const world = new String('world! '); + const nice = new String('Have a nice day.'); + "#; + forward(&mut engine, init); + let a = forward(&mut engine, "hello.concat(world, nice)"); + let b = forward(&mut engine, "hello + world + nice"); + // Todo: fix this + //assert_eq!(a, String::from("Hello, world! Have a nice day.")); + //assert_eq!(b, String::from("Hello, world! Have a nice day.")); + } + + #[test] + fn repeat() { + let mut engine = Executor::new(); + let init = r#" + const empty = new String(''); + const en = new String('english'); + const zh = new String('中文'); + "#; + forward(&mut engine, init); + + let empty = String::from(""); + assert_eq!(forward(&mut engine, "empty.repeat(0)"), empty); + assert_eq!(forward(&mut engine, "empty.repeat(1)"), empty); + + assert_eq!(forward(&mut engine, "en.repeat(0)"), empty); + assert_eq!(forward(&mut engine, "zh.repeat(0)"), empty); + + assert_eq!( + forward(&mut engine, "en.repeat(1)"), + String::from("english") + ); + assert_eq!( + forward(&mut engine, "zh.repeat(2)"), + String::from("中文中文") + ); + } + + #[test] + fn starts_with() { + let mut engine = Executor::new(); + let init = r#" + const empty = new String(''); + const en = new String('english'); + const zh = new String('中文'); + "#; + forward(&mut engine, init); + let pass = String::from("true"); + assert_eq!(forward(&mut engine, "empty.startsWith('')"), pass); + assert_eq!(forward(&mut engine, "en.startsWith('e')"), pass); + assert_eq!(forward(&mut engine, "zh.startsWith('中')"), pass); + } + + #[test] + fn ends_with() { + let mut engine = Executor::new(); + let init = r#" + const empty = new String(''); + const en = new String('english'); + const zh = new String('中文'); + "#; + forward(&mut engine, init); + let pass = String::from("true"); + assert_eq!(forward(&mut engine, "empty.endsWith('')"), pass); + assert_eq!(forward(&mut engine, "en.endsWith('h')"), pass); + assert_eq!(forward(&mut engine, "zh.endsWith('文')"), pass); + } } diff --git a/src/lib/lib.rs b/src/lib/lib.rs index c403d660ea..68a4c8ee50 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -33,6 +33,7 @@ pub mod js; pub mod syntax; use crate::exec::{Executor, Interpreter}; +use crate::syntax::ast::expr::Expr; use crate::syntax::lexer::Lexer; use crate::syntax::parser::Parser; use wasm_bindgen::prelude::*; @@ -45,15 +46,18 @@ extern "C" { fn log(s: &str); } -pub fn exec(src: &str) -> String { +fn parser_expr(src: &str) -> Expr { let mut lexer = Lexer::new(src); lexer.lex().unwrap(); let tokens = lexer.tokens; + Parser::new(tokens).parse_all().unwrap() +} +/// Execute the code using an existing Interpreter +/// The str is consumed and the state of the Interpreter is changed +pub fn forward(engine: &mut Interpreter, src: &str) -> String { // Setup executor - let expr = Parser::new(tokens).parse_all().unwrap(); - - let mut engine: Interpreter = Executor::new(); + let expr = parser_expr(src); let result = engine.run(&expr); match result { Ok(v) => v.to_string(), @@ -61,6 +65,12 @@ pub fn exec(src: &str) -> String { } } +/// Create a clean Interpreter and execute the code +pub fn exec(src: &str) -> String { + let mut engine: Interpreter = Executor::new(); + forward(&mut engine, src) +} + #[wasm_bindgen] pub fn evaluate(src: &str) -> String { let mut lexer = Lexer::new(&src); @@ -72,7 +82,7 @@ pub fn evaluate(src: &str) -> String { let tokens = lexer.tokens; // Setup executor - let expr: syntax::ast::expr::Expr; + let expr: Expr; match Parser::new(tokens).parse_all() { Ok(v) => {