diff --git a/README.md b/README.md index c5706799..411c3bc5 100644 --- a/README.md +++ b/README.md @@ -44,61 +44,16 @@ a link to this page somewhere in the documentation/system about section. ## Change Log for the BPjs Library. -### 2020-12-06 -* :sparkles: BProgram data storage ready (closes[#123](https://github.com/bThink-BGU/BPjs/issues/123)). +### 2021-01 -### 2020-12-04 -* :bug: Forking now handles b-thread data properly. +* :arrow_up: Logging now allows formatting, using Java's MessageFormat. +* :arrow_up: Expressive `toString` on JavaScript sets ([#135](https://github.com/bThink-BGU/BPjs/issues/135)). +* :arrow_up: Improved logging consistency ([#132](https://github.com/bThink-BGU/BPjs/issues/132)). +* :bug: Fixed the note color issue in the documentation ([#137](https://github.com/bThink-BGU/BPjs/issues/137)). +* :arrow_up: Error messages when passing non-events to a synchronization statement are more informative (([#131](https://github.com/bThink-BGU/BPjs/issues/131)). -### 2020-11-15 -* :sparkles: A B-Program can have multiple b-threads with the same name (closes[#112](https://github.com/bThink-BGU/BPjs/issues/112)). -### 2020-11-09 -* :arrow_up: Trace's `getLastEvent` now return an `Optional` rather than an `Event`. This is because some traces may not have a last event (part of the fix to [#122](https://github.com/bThink-BGU/BPjs/issues/122)). - -### 2020-10-20 -* :arrows_counterclockwise: `BEvent::toString` is more JS-like. - -### 2020-10-10 -* :arrows_counterclockwise: Internal refactorings. -* :bug: JavaScript errors (regarding syntax and references) are reported, and halt both execution and analysis [#94](https://github.com/bThink-BGU/BPjs/issues/94). - - -### 2020-09-23 -* :tada: B-Threads have a data object, available to b-thread code via `bp.thread.data`. This object can be set, updated, and - replaced during execution. It can also be pre-set on b-thread creation [#106](https://github.com/bThink-BGU/BPjs/issues/106). -* :sparkles: DFS scan that uses forgetful state storage is not resilient to cyclic state graphs [#95](https://github.com/bThink-BGU/BPjs/issues/95). - -### 2020-09-22 -* :arrow_up: Added a b-thread name for the self-blocking warning [#103](https://github.com/bThink-BGU/BPjs/issues/103). -* :arrow_up: Logging supports compound objects [#102](https://github.com/bThink-BGU/BPjs/issues/102). -* :sparkles: B-thread name is available to JavaScript code. -* :bug: All JS code is evaluated as ES6 (formerly, evaluations where optimized ES1.8). -* :bug: Improved self-blocking warning logic. - -### 2020-08-10 -* :put_litter_in_its_place: Unification of some simple event sets, e.g. `[es1]` is identical to `es1`, `[]` to `bp.none`, etc. -* :put_litter_in_its_place: Removed some printouts. - -### 2020-08-09 -* :arrow_up: Updated to use Rhino 1.7.13 -* :arrow_up: Updated to Java 11 -* :sparkles: A new type of violation: hot run, which looks at hot runs of a subset of a b-program b-threads. - Previously, BPjs supported all b-threads ("hot system run") or just a single one ("hot b-thread run"). -* :part_alternation_mark: Liveness verification parts now aligned semantically with current theory. -* :put_litter_in_its_place: Code and documentation clean-ups. - -### 2020-04-22 -* :arrow_up: Updated to use Rhino 1.7.12 -* :arrow_up: :tada: Can use ES6 syntax, such as `let` and `const`. - * No arrow functions yet, there are some issues with continuations and arrow functions. -* :arrow_up: Synchronization statements are created more efficiently. -* :bug: `ComposableEventSet` checks arguments on creation, so that it can't be instantiated with `null` events (this later causes NPEs). -* :bug: `PrioritizedBThread` ESS fixed ([#70](https://github.com/bThink-BGU/BPjs/issues/70). -* :put_litter_in_its_place: Various small code clean-ups. -* :put_litter_in_its_place: Removed extraneous dependencies from `pom.xml` - -[Earlier Changes](changelog-2019.md) +[Earlier Changes](changelog-2020.md) Legend: * :arrows_counterclockwise: Change diff --git a/changelog-2020.md b/changelog-2020.md new file mode 100644 index 00000000..d3cc90e1 --- /dev/null +++ b/changelog-2020.md @@ -0,0 +1,66 @@ +# Changelog for 2020 + +### 2020-12-06 +* :sparkles: BProgram data storage ready (closes[#123](https://github.com/bThink-BGU/BPjs/issues/123)). + +### 2020-12-04 +* :bug: Forking now handles b-thread data properly. + +### 2020-11-15 +* :sparkles: A B-Program can have multiple b-threads with the same name (closes[#112](https://github.com/bThink-BGU/BPjs/issues/112)). + +### 2020-11-09 +* :arrow_up: Trace's `getLastEvent` now return an `Optional` rather than an `Event`. This is because some traces may not have a last event (part of the fix to [#122](https://github.com/bThink-BGU/BPjs/issues/122)). + +### 2020-10-20 +* :arrows_counterclockwise: `BEvent::toString` is more JS-like. + +### 2020-10-10 +* :arrows_counterclockwise: Internal refactorings. +* :bug: JavaScript errors (regarding syntax and references) are reported, and halt both execution and analysis [#94](https://github.com/bThink-BGU/BPjs/issues/94). + + +### 2020-09-23 +* :tada: B-Threads have a data object, available to b-thread code via `bp.thread.data`. This object can be set, updated, and + replaced during execution. It can also be pre-set on b-thread creation [#106](https://github.com/bThink-BGU/BPjs/issues/106). +* :sparkles: DFS scan that uses forgetful state storage is not resilient to cyclic state graphs [#95](https://github.com/bThink-BGU/BPjs/issues/95). + +### 2020-09-22 +* :arrow_up: Added a b-thread name for the self-blocking warning [#103](https://github.com/bThink-BGU/BPjs/issues/103). +* :arrow_up: Logging supports compound objects [#102](https://github.com/bThink-BGU/BPjs/issues/102). +* :sparkles: B-thread name is available to JavaScript code. +* :bug: All JS code is evaluated as ES6 (formerly, evaluations where optimized ES1.8). +* :bug: Improved self-blocking warning logic. + +### 2020-08-10 +* :put_litter_in_its_place: Unification of some simple event sets, e.g. `[es1]` is identical to `es1`, `[]` to `bp.none`, etc. +* :put_litter_in_its_place: Removed some printouts. + +### 2020-08-09 +* :arrow_up: Updated to use Rhino 1.7.13 +* :arrow_up: Updated to Java 11 +* :sparkles: A new type of violation: hot run, which looks at hot runs of a subset of a b-program b-threads. + Previously, BPjs supported all b-threads ("hot system run") or just a single one ("hot b-thread run"). +* :part_alternation_mark: Liveness verification parts now aligned semantically with current theory. +* :put_litter_in_its_place: Code and documentation clean-ups. + +### 2020-04-22 +* :arrow_up: Updated to use Rhino 1.7.12 +* :arrow_up: :tada: Can use ES6 syntax, such as `let` and `const`. + * No arrow functions yet, there are some issues with continuations and arrow functions. +* :arrow_up: Synchronization statements are created more efficiently. +* :bug: `ComposableEventSet` checks arguments on creation, so that it can't be instantiated with `null` events (this later causes NPEs). +* :bug: `PrioritizedBThread` ESS fixed ([#70](https://github.com/bThink-BGU/BPjs/issues/70). +* :put_litter_in_its_place: Various small code clean-ups. +* :put_litter_in_its_place: Removed extraneous dependencies from `pom.xml` + +[Earlier Changes](changelog-2019.md) + +Legend: +* :arrows_counterclockwise: Change +* :sparkles: New feature +* :tada: New feature, but more exciting +* :part_alternation_mark: Refactor (turns out this sign is called "part alternation mark" and not "weird 'M'", so it fits). +* :put_litter_in_its_place: Deprecation +* :arrow_up: Upgrade +* :bug: Bug fix diff --git a/docs/source/BPjsTutorial/code/logging-with-data.js b/docs/source/BPjsTutorial/code/logging-with-data.js new file mode 100644 index 00000000..10fc51fd --- /dev/null +++ b/docs/source/BPjsTutorial/code/logging-with-data.js @@ -0,0 +1,11 @@ +const obj = {hello: "World", idioms:["request","waitFor","block"]}; +const stuff = new Set(); +stuff.add("thing 1"); +stuff.add("thing 2"); +stuff.add("thing 42"); + +bp.log.info("Here is field hello: {0} of object {1}", obj.hello, obj); +bp.log.info("Here is are some {1}: {0}", stuff, "stuff"); + +bp.log.info("I have a {0,number} reasons to block this event.", 1000000); +bp.log.info("{0} {0,number,#.##} {0,number,#.####}", 3.14159); \ No newline at end of file diff --git a/docs/source/BPjsTutorial/code/logging.js b/docs/source/BPjsTutorial/code/logging.js index 1ece02bd..99ea8fa6 100644 --- a/docs/source/BPjsTutorial/code/logging.js +++ b/docs/source/BPjsTutorial/code/logging.js @@ -1,4 +1,4 @@ -bp.log.info("registering bthreads - start"); +bp.log.info("registering b-threads - start"); var LOG_LEVELS = ["Fine", "Info", "Warn", "Off"]; bp.registerBThread( "event generator", function(){ @@ -19,4 +19,4 @@ bp.registerBThread( "event logging", function() { } }) -bp.log.info("registering bthreads - done"); +bp.log.info("registering b-threads - done"); diff --git a/docs/source/BPjsTutorial/logging.rst b/docs/source/BPjsTutorial/logging.rst index 0edf89cd..b8d21591 100644 --- a/docs/source/BPjsTutorial/logging.rst +++ b/docs/source/BPjsTutorial/logging.rst @@ -12,13 +12,13 @@ As our programs become more complex, some logging/``printf`` capabilities are ne The "event generator" creates four events, one for each logging level. The "event logging" b-thread waits for all events (note the use of ``bp.all``), sets the logger level (line 14), and attempts to log in all three log methods (lines 15-17). Here is the program output. Note that after event 3, where the level is set to ``"Off"``, no messages are emitted at all. -.. code:: bash +.. code:: # [READ] /.../logging.js - [JS][Info] registering bthreads - start + [JS][Info] registering b-threads - start -:BPjs Added event generator -:BPjs Added event logging - [JS][Info] registering bthreads - done + [JS][Info] registering b-threads - done # [ OK ] logging.js ---:BPjs Started --:BPjs Event [BEvent name:event 0] @@ -35,11 +35,26 @@ The "event generator" creates four events, one for each logging level. The "even ---:BPjs Ended +.. caution :: Later versions might integrate BPjs with a full-blown logging system, such as `logback`_ or `log4j2`_. Programs relying on the exact logging format may need to change once the logging is updated. If you need to write a program that relies on accurate interpretation a b-program life cycle and selected events, consider implementing a `BProgramRunnerListener`_. +Message Formatting +------------------ +The BPjs logger formats messages using Java's `MessageFormat`_. Under the hood, JavaScript objects are printed using a special formatter, which gives more information that the default cryptic ``[object object]``. The code below contains some formatting examples: -.. caution :: Later versions might integrate BPjs with a full-blown logging system, such as `logback`_ or `log4j2`_. Programs relying on the exact logging format may need to change once the logging is updated. If you need to write a program that relies on accurate interpretation a b-program life cycle and selected events, consider implementing a `BProgramRunnerListener`_. +.. literalinclude:: code/logging-with-data.js + :linenos: + :language: javascript + + +.. code:: + + [BP][Info] Here is field hello: World of object {JS_Obj hello:"World", idioms:[JS_Array 0:"request" | 1:"waitFor" | 2:"block"]} + [BP][Info] Here is are some stuff: {JS_Set "thing 1", "thing 2", "thing 42"} + [BP][Info] I have a 1,000,000 reasons to block this event. + [BP][Info] 3.142 3.14 3.1416 .. _logback: https://logback.qos.ch .. _log4j2: http://logging.apache.org/log4j/2.x/index.html .. _BProgramRunnerListener: http://javadoc.io/page/com.github.bthink-bgu/BPjs/latest/il/ac/bgu/cs/bp/bpjs/execution/listeners/BProgramRunnerListener.html +.. _MessageFormat: https://docs.oracle.com/javase/8/docs/api/java/text/MessageFormat.html \ No newline at end of file diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css index aa2d5ef5..8df9dec9 100644 --- a/docs/source/_static/css/custom.css +++ b/docs/source/_static/css/custom.css @@ -38,8 +38,8 @@ th { } .danger { - background-color: rgba(255,100,100,.8); - color: rgb(146, 146, 0); + background-color: rgba(255, 58, 58, 0.8); + color: rgb(255, 255, 0); } .admonition-todo { diff --git a/pom.xml b/pom.xml index dafb2aaa..6a558474 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.github.bthink-bgu BPjs - 0.11.0 + 0.11.1-SNAPSHOT jar BPjs @@ -40,8 +40,9 @@ UTF-8 + UTF-8 11 - il.ac.bgu.cs.bp.bpjs.mains.BPJsCliRunner + il.ac.bgu.cs.bp.bpjs.mains.BPjsCliRunner 1.2.0 2.3.0 @@ -112,34 +113,6 @@ 11 - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - - javax.xml.bind - jaxb-api - 2.2.3 - - - - - org.jacoco - jacoco-maven-plugin - 0.8.6 - - - prepare-agent - - prepare-agent - - - - - org.apache.maven.plugins maven-jar-plugin @@ -147,7 +120,8 @@ - il.ac.bgu.cs.bp.bpjs.mains.BPJsCliRunner + ${project.properties.exec.mainClass} + true @@ -168,6 +142,33 @@ + + + org.eluder.coveralls + coveralls-maven-plugin + 4.3.0 + + + javax.xml.bind + jaxb-api + 2.2.3 + + + + + org.jacoco + jacoco-maven-plugin + 0.8.6 + + + prepare-agent + + prepare-agent + + + + + diff --git a/src/main/java/il/ac/bgu/cs/bp/bpjs/execution/jsproxy/BProgramJsProxy.java b/src/main/java/il/ac/bgu/cs/bp/bpjs/execution/jsproxy/BProgramJsProxy.java index 1cf53811..3dc06222 100644 --- a/src/main/java/il/ac/bgu/cs/bp/bpjs/execution/jsproxy/BProgramJsProxy.java +++ b/src/main/java/il/ac/bgu/cs/bp/bpjs/execution/jsproxy/BProgramJsProxy.java @@ -8,6 +8,7 @@ import il.ac.bgu.cs.bp.bpjs.model.eventsets.JsEventSet; import il.ac.bgu.cs.bp.bpjs.exceptions.BPjsRuntimeException; import il.ac.bgu.cs.bp.bpjs.execution.tasks.FailedAssertionException; +import il.ac.bgu.cs.bp.bpjs.internal.ScriptableUtils; import il.ac.bgu.cs.bp.bpjs.model.BProgramSyncSnapshot; import il.ac.bgu.cs.bp.bpjs.model.SyncStatement; import il.ac.bgu.cs.bp.bpjs.model.ForkStatement; @@ -240,18 +241,22 @@ void synchronizationPoint( NativeObject jsRWB, Boolean hot, Object data ) { } Object req = jRWB.get("request"); if ( req != null ) { - if ( req instanceof BEvent ) { - stmt = stmt.request((BEvent)req); - } else if ( req instanceof NativeArray ) { - NativeArray arr = (NativeArray) req; - stmt = stmt.request(arr.getIndexIds().stream() - .map( i -> (BEvent)arr.get(i) ) - .collect( toList() )); - } + try { + if ( req instanceof NativeArray ) { + NativeArray arr = (NativeArray) req; + stmt = stmt.request(arr.getIndexIds().stream() + .map( i -> (BEvent)arr.get(i) ) + .collect( toList() )); + } else { + stmt = stmt.request((BEvent)req); + } + } catch (ClassCastException cce ) { + throw new BPjsRuntimeException("A non-event object requested in a sync statement. Offending object:'" + ScriptableUtils.stringify(req) + "'"); + } } - EventSet waitForSet = convertToEventSet(jRWB.get("waitFor")); - EventSet blockSet = convertToEventSet(jRWB.get("block")); + EventSet waitForSet = convertToEventSet(jRWB.get("waitFor")); + EventSet blockSet = convertToEventSet(jRWB.get("block")); EventSet interruptSet = convertToEventSet(jRWB.get("interrupt")); stmt = stmt.waitFor( waitForSet ) .block( blockSet ) @@ -284,8 +289,7 @@ private EventSet convertToEventSet( Object jsObject ) { .collect( toSet() ) ); } else { final String errorMessage = "Cannot convert " + jsObject + " of class " + jsObject.getClass() + " to an event set"; - Logger.getLogger(BThreadSyncSnapshot.class.getName()).log(Level.SEVERE, errorMessage); - throw new IllegalArgumentException(errorMessage); + throw new BPjsRuntimeException(errorMessage); } } diff --git a/src/main/java/il/ac/bgu/cs/bp/bpjs/execution/jsproxy/BpLog.java b/src/main/java/il/ac/bgu/cs/bp/bpjs/execution/jsproxy/BpLog.java index dafb055d..55c093c6 100644 --- a/src/main/java/il/ac/bgu/cs/bp/bpjs/execution/jsproxy/BpLog.java +++ b/src/main/java/il/ac/bgu/cs/bp/bpjs/execution/jsproxy/BpLog.java @@ -28,6 +28,7 @@ import java.text.MessageFormat; import java.util.Arrays; +import java.util.Set; /** * Simple logging mechanism for {@link BProgram}s. @@ -53,11 +54,12 @@ public void fine(Object msg, Object ...args) { log(LogLevel.Fine, msg, args); } - public void log(LogLevel lvl, Object msg, Object... args) { + public void log(LogLevel lvl, Object msg, Object ...args) { if (level.compareTo(lvl) >= 0) { System.out.println("[BP][" + lvl.name() + "] " + - (args.length == 0 ? ScriptableUtils.stringify(msg) : - MessageFormat.format(ScriptableUtils.stringify(msg), Arrays.stream(args).map(ScriptableUtils::stringify).toArray()))); + (((args==null)||(args.length > 0)) + ? MessageFormat.format( (msg!=null ? msg.toString():""), Arrays.stream(args).map(this::formatArg).toArray()) + : ScriptableUtils.stringify(msg))); } } @@ -71,4 +73,12 @@ public String getLevel() { return level.name(); } + private static final Set PASS_THROUGH = Set.of(Integer.class, Long.class, Short.class, Double.class, Float.class); + + private Object formatArg(Object arg) { + if ( arg == null ) return arg; + if ( PASS_THROUGH.contains(arg.getClass()) ) return arg; + return ScriptableUtils.stringify(arg); + } + } diff --git a/src/main/java/il/ac/bgu/cs/bp/bpjs/execution/tasks/BPEngineTask.java b/src/main/java/il/ac/bgu/cs/bp/bpjs/execution/tasks/BPEngineTask.java index d6c52470..8cf17386 100644 --- a/src/main/java/il/ac/bgu/cs/bp/bpjs/execution/tasks/BPEngineTask.java +++ b/src/main/java/il/ac/bgu/cs/bp/bpjs/execution/tasks/BPEngineTask.java @@ -1,6 +1,7 @@ package il.ac.bgu.cs.bp.bpjs.execution.tasks; import il.ac.bgu.cs.bp.bpjs.exceptions.BPjsCodeEvaluationException; +import il.ac.bgu.cs.bp.bpjs.exceptions.BPjsException; import il.ac.bgu.cs.bp.bpjs.exceptions.BPjsRuntimeException; import il.ac.bgu.cs.bp.bpjs.execution.jsproxy.BProgramJsProxy; import il.ac.bgu.cs.bp.bpjs.execution.jsproxy.BProgramJsProxy.CapturedBThreadState; @@ -152,6 +153,8 @@ private BThreadSyncSnapshot handleWrappedException(WrappedException wfae) throws FailedAssertionViolation fa = new FailedAssertionViolation( fae.getMessage(), btss.getName() ); listener.assertionFailed( fa ); return null; + } else if (wfae.getCause() instanceof BPjsException) { + throw (BPjsException)wfae.getCause(); } else { throw wfae; } diff --git a/src/main/java/il/ac/bgu/cs/bp/bpjs/internal/ScriptableUtils.java b/src/main/java/il/ac/bgu/cs/bp/bpjs/internal/ScriptableUtils.java index 6c4630b9..7c1c9ed7 100644 --- a/src/main/java/il/ac/bgu/cs/bp/bpjs/internal/ScriptableUtils.java +++ b/src/main/java/il/ac/bgu/cs/bp/bpjs/internal/ScriptableUtils.java @@ -71,7 +71,7 @@ public static boolean jsEquals(Object o1, Object o2) { return jsScriptableObjectEqual((ScriptableObject) o1, (ScriptableObject) o2); } - // Concatenated strings in Rhino have a different type. We need to manually + // Concatenated strings in Rhino are not java.lang.Strings. We need to manually // resolve to String semantics, which is what the following lines do. if (o1 instanceof ConsString) { o1 = o1.toString(); @@ -91,36 +91,45 @@ public static boolean jsScriptableObjectEqual(ScriptableObject o1, ScriptableObj return o1Ids.equals(o2Ids) && o1Ids.stream().allMatch(id -> jsEquals(o1.get(id), o2.get(id))); } - + public static String stringify( Object o ) { + return stringify(o, false); + } + + public static String stringify( Object o, boolean quoteStrings) { if ( o == null ) return ""; - if ( o instanceof String ) return "\""+o + "\""; - if ( o instanceof ConsString ) return "\"" + ((ConsString)o).toString() + "\""; + if ( o instanceof String ) return quoteStrings ? "\""+o + "\"" : (String)o; + if ( o instanceof ConsString ) return quoteStrings ? "\"" + ((ConsString)o).toString() + "\"" : ((ConsString)o).toString(); if ( o instanceof NativeArray) { NativeArray arr = (NativeArray) o; - return arr.getIndexIds().stream().map( id -> id + ":" + stringify(arr.get(id)) ).collect(joining(" | ", "[JS_Array ", "]")); + return arr.getIndexIds().stream().map( id -> id + ":" + stringify(arr.get(id), true) ).collect(joining(" | ", "[JS_Array ", "]")); + } + if ( o instanceof NativeSet ) { + NativeSet ns = (NativeSet) o; + return "{JS_Set " + toString(ns) + "}"; } if ( o instanceof ScriptableObject ) { ScriptableObject sob = (ScriptableObject) o; - return Arrays.stream(sob.getIds()).map( id -> id + ": " + stringify(sob.get(id)) ).collect( joining(", Cha", "{JS_Obj ", "}")); + return Arrays.stream(sob.getIds()).map( id -> id + ":" + stringify(sob.get(id), true) ).collect( joining(", ", "{JS_Obj ", "}")); } if ( o instanceof Object[] ) { Object[] objArr = (Object[]) o; - return IntStream.range(0, objArr.length).mapToObj(idx -> stringify(objArr[idx])).collect(joining("|","[Java_Array ", "]")); + return IntStream.range(0, objArr.length).mapToObj(idx -> stringify(objArr[idx], true)).collect(joining(" | ","[J_Array ", "]")); } if ( o instanceof Map) { Map mp = (Map) o; - return mp.entrySet().stream().map( e -> e.getKey()+"->" + stringify(e.getValue())).collect(joining(",","{Map ", "}")); + return mp.entrySet().stream().map( e -> e.getKey()+"->" + stringify(e.getValue(), true)).collect(joining(",","{J_Map ", "}")); } if ( o instanceof List ) { List ls = (List) o; - return ls.stream().map(ScriptableUtils::stringify).collect(joining(", ","")); + return ls.stream().map( e->stringify(e,true) ).collect(joining(", ","")); } if ( o instanceof Set ) { Set ls = (Set) o; - return ls.stream().map(ScriptableUtils::stringify).collect(joining(", ","{Set ", "}")); + return ls.stream().map( e->stringify(e,true) ).collect(joining(", ","{J_Set ", "}")); } return o.toString(); + } @@ -156,4 +165,33 @@ public static int jsScriptableObjectHashCode(ScriptableObject jsObj) { return acc[0]; } + + /** + * A problematic-yet-working way of getting a meanningful toString + * on a NativeSet. + * @param ns + * @return a textual description of {@code ns}. + */ + private static String toString(NativeSet ns) { + + String code = "const arr=[]; ns.forEach(e=>arr.push(e)); arr"; + + try { + Context curCtx = Context.enter(); + curCtx.setLanguageVersion(Context.VERSION_ES6); + ImporterTopLevel importer = new ImporterTopLevel(curCtx); + Scriptable tlScope = curCtx.initStandardObjects(importer); + tlScope.put("ns", tlScope, ns); + Object resultObj = curCtx.evaluateString( + tlScope, code, + "", 1, null); + + NativeArray arr = (NativeArray) resultObj; + return arr.getIndexIds().stream().map( id -> stringify(arr.get(id), true) ).collect(joining(", ")); + + } finally { + Context.exit(); + } + + } } diff --git a/src/main/java/il/ac/bgu/cs/bp/bpjs/mains/BPJsCliRunner.java b/src/main/java/il/ac/bgu/cs/bp/bpjs/mains/BPJsCliRunner.java index 6449fcd9..dc9ea5c6 100644 --- a/src/main/java/il/ac/bgu/cs/bp/bpjs/mains/BPJsCliRunner.java +++ b/src/main/java/il/ac/bgu/cs/bp/bpjs/mains/BPJsCliRunner.java @@ -58,7 +58,7 @@ * * @author michael */ -public class BPJsCliRunner { +public class BPjsCliRunner { public static void main(String[] args) throws Exception { if (args.length == 0) { @@ -90,7 +90,7 @@ protected void setupProgramScope(Scriptable scope) { logScriptExceptionAndQuit(ee, arg); } catch (IOException ex) { println("Exception while processing " + arg + ": " + ex.getMessage()); - Logger.getLogger(BPJsCliRunner.class.getName()).log(Level.SEVERE, null, ex); + Logger.getLogger(BPjsCliRunner.class.getName()).log(Level.SEVERE, null, ex); } } } diff --git a/src/main/java/il/ac/bgu/cs/bp/bpjs/model/BEvent.java b/src/main/java/il/ac/bgu/cs/bp/bpjs/model/BEvent.java index ed2fefb0..ea420083 100644 --- a/src/main/java/il/ac/bgu/cs/bp/bpjs/model/BEvent.java +++ b/src/main/java/il/ac/bgu/cs/bp/bpjs/model/BEvent.java @@ -2,11 +2,9 @@ import il.ac.bgu.cs.bp.bpjs.internal.ScriptableUtils; import il.ac.bgu.cs.bp.bpjs.model.eventsets.EventSet; -import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; -import org.mozilla.javascript.Scriptable; /** * A base class for events. Each event has a name and optional data, which is a @@ -23,13 +21,13 @@ public class BEvent implements Comparable, EventSet, java.io.Serializabl private static final ConcurrentHashMap NAME_INDICES =new ConcurrentHashMap<>(); /** - * Name of the event. Public access, so that the Javascript code feels + * Name of the event. Public access, so that the JavaScript code feels * natural. */ public final String name; /** - * Extra data for the event. Public access, so that the Javascript code + * Extra data for the event. Public access, so that the JavaScript code * feels natural. */ public final Object maybeData; @@ -72,8 +70,9 @@ public Optional getDataField() { } /** - * A JavaScript accessor for the event's data. If you are using this method - * from Java code, you may want to consider using {@link #getDataField()}. + * A JavaScript accessor for the event's data, which may be @{code null}. + * If you are using this method from Java code, you may want to consider + * using {@link #getDataField()}, for a more Java-friendly API. * * @return the event's data, or {@code null}. * @see #getDataField() diff --git a/src/main/java/il/ac/bgu/cs/bp/bpjs/model/BProgramSyncSnapshot.java b/src/main/java/il/ac/bgu/cs/bp/bpjs/model/BProgramSyncSnapshot.java index a82f8d9f..e07797c2 100644 --- a/src/main/java/il/ac/bgu/cs/bp/bpjs/model/BProgramSyncSnapshot.java +++ b/src/main/java/il/ac/bgu/cs/bp/bpjs/model/BProgramSyncSnapshot.java @@ -32,6 +32,7 @@ import org.mozilla.javascript.ContinuationPending; import org.mozilla.javascript.EcmaError; import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.WrappedException; /** * The state of a {@link BProgram} when all its BThreads are at {@code bsync}. @@ -190,6 +191,9 @@ public BProgramSyncSnapshot triggerEvent(BEvent anEvent, ExecutorService exSvc, if ( cause instanceof ExecutionException ) { cause = cause.getCause(); } + if ( cause instanceof WrappedException ) { + cause = cause.getCause(); + } if ( cause instanceof BPjsException ) { throw (BPjsException)cause; @@ -315,8 +319,7 @@ public boolean isHot() { private BThreadSyncSnapshot safeGet(Future fbss) { try { return fbss.get(); - } catch (InterruptedException | ExecutionException ex) { - Logger.getLogger(BProgramSyncSnapshot.class.getName()).log(Level.SEVERE, null, ex); + } catch (InterruptedException | ExecutionException | WrappedException ex) { if ( ex.getCause() instanceof BPjsException ) { throw (BPjsException)ex.getCause(); } else { diff --git a/src/test/java/il/ac/bgu/cs/bp/bpjs/BrokenCodeTest.java b/src/test/java/il/ac/bgu/cs/bp/bpjs/BrokenCodeTest.java index 9a46dbf2..43a8abb7 100644 --- a/src/test/java/il/ac/bgu/cs/bp/bpjs/BrokenCodeTest.java +++ b/src/test/java/il/ac/bgu/cs/bp/bpjs/BrokenCodeTest.java @@ -26,8 +26,11 @@ import il.ac.bgu.cs.bp.bpjs.analysis.DfsBProgramVerifier; import il.ac.bgu.cs.bp.bpjs.exceptions.BPjsCodeEvaluationException; import il.ac.bgu.cs.bp.bpjs.execution.BProgramRunner; +import il.ac.bgu.cs.bp.bpjs.execution.listeners.BProgramRunnerListenerAdapter; import il.ac.bgu.cs.bp.bpjs.model.BProgram; import il.ac.bgu.cs.bp.bpjs.model.ResourceBProgram; +import java.util.concurrent.atomic.AtomicBoolean; +import static org.junit.Assert.assertTrue; import org.junit.Test; /** @@ -83,5 +86,47 @@ public void brokenSyntaxDfsTest() throws Exception { vfr.verify(bp); } + @Test() + public void requestNonEvent() throws Exception { + testRuntimeError(1, "NON_EVENT"); + } + + @Test() + public void requestNonEventInArray() throws Exception { + testRuntimeError(2, "NON_EVENT"); + } + + @Test() + public void waitedForNonEvent() throws Exception { + testRuntimeError(3, "NON_EVENT_or_SET"); + } + + @Test() + public void blockedForNonEvent() throws Exception { + testRuntimeError(4, "NON_EVENT_or_SET"); + } + @Test() + public void interruptingForNonEvent() throws Exception { + testRuntimeError(5, "NON_EVENT_or_SET"); + } + + + public void testRuntimeError(int swValue, String expectedInReport) throws Exception { + BProgram bp = new ResourceBProgram("broken/requestNonEvents.js"); + BProgramRunner rnr = new BProgramRunner(); + rnr.setBProgram(bp); + final AtomicBoolean exceptionReported = new AtomicBoolean(); + rnr.addListener(new BProgramRunnerListenerAdapter() { + @Override + public void error(BProgram bp, Exception ex) { + assertTrue("Message expected to contain '" + expectedInReport + "'",ex.getMessage().contains(expectedInReport)); + exceptionReported.set(true); + } + }); + bp.putInGlobalScope("sw", swValue); + rnr.run(); + + assertTrue(exceptionReported.get()); + } } diff --git a/src/test/java/il/ac/bgu/cs/bp/bpjs/execution/LoggingTest.java b/src/test/java/il/ac/bgu/cs/bp/bpjs/execution/LoggingTest.java index bbfdc6aa..c286dd93 100644 --- a/src/test/java/il/ac/bgu/cs/bp/bpjs/execution/LoggingTest.java +++ b/src/test/java/il/ac/bgu/cs/bp/bpjs/execution/LoggingTest.java @@ -60,6 +60,36 @@ public void testLogLevels() throws InterruptedException, UnsupportedEncodingExce baos.close(); } + @Test + public void testFormatting() throws InterruptedException, UnsupportedEncodingException, IOException { + PrintStream originalOut = System.out; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (PrintStream myOut = new PrintStream(baos)) { + System.setOut(myOut); + new BProgramRunner( new StringBProgram( + "bp.log.info(\"{0}\",1000);\n" + + "bp.log.info(\"{0}\",{a:1,b:2, c:\"banana\", arr:[1,2,3]});\n" + + "bp.log.info(\"0\");\n" + + "bp.log.info(null);\n" + + "")).run(); + myOut.flush(); + } + String result = baos.toString(StandardCharsets.UTF_8.name()); + System.setOut(originalOut); + baos.close(); + + System.out.println("result:"); + System.out.println(result); + + String[] lines = result.split("\n"); + assertTrue(lines[0].contains("1,000") ); + assertTrue(lines[1].contains("a:1") ); + assertTrue(lines[1].contains("b:2") ); + assertTrue(lines[2].contains("0") ); + assertTrue(lines[3].contains("null") ); + } + + @Test public void testExternalSetLogLevel() throws UnsupportedEncodingException, IOException { PrintStream originalOut = System.out; @@ -109,20 +139,20 @@ public void testExternalSetLogLevel() throws UnsupportedEncodingException, IOExc @Test public void testCompoundObjectLogging() throws IOException { PrintStream originalOut = System.out; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); + String result; - - try (PrintStream myOut = new PrintStream(baos)) { - System.setOut(myOut); - - new BProgramRunner(new ResourceBProgram("logging/compound.js")).run(); - myOut.flush(); - - } finally { - System.setOut(originalOut); + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + try (PrintStream myOut = new PrintStream(baos)) { + System.setOut(myOut); + + new BProgramRunner(new ResourceBProgram("logging/compound.js")).run(); + myOut.flush(); + + } finally { + System.setOut(originalOut); + } + result = baos.toString(StandardCharsets.UTF_8); } - String result = baos.toString(StandardCharsets.UTF_8); - baos.close(); System.out.println(result); diff --git a/src/test/java/il/ac/bgu/cs/bp/bpjs/internal/ScriptableUtilsTest.java b/src/test/java/il/ac/bgu/cs/bp/bpjs/internal/ScriptableUtilsTest.java index aa7610ae..968ab808 100644 --- a/src/test/java/il/ac/bgu/cs/bp/bpjs/internal/ScriptableUtilsTest.java +++ b/src/test/java/il/ac/bgu/cs/bp/bpjs/internal/ScriptableUtilsTest.java @@ -41,22 +41,48 @@ public class ScriptableUtilsTest { * Test of toString method, of class ScriptableUtils. */ @Test - public void testToString() { - Context curCtx; + public void testToStringJSObject() { + Scriptable aScope = evaluateJS("var a={}; a"); + String expResult = "{JS_Obj }"; + String result = ScriptableUtils.toString(aScope); + assertEquals(expResult, result); + + aScope = evaluateJS("var a={x:\"hello\"}; a"); + expResult = "{JS_Obj x:\"hello\"}"; + result = ScriptableUtils.toString(aScope); + assertEquals(expResult, result); + + aScope = evaluateJS("var a={x:\"hello\", y:\"world\"}; a"); + expResult = "{JS_Obj x:\"hello\", y:\"world\"}"; + result = ScriptableUtils.toString(aScope); + assertEquals(expResult, result); + } + + @Test + public void testToStringJSSet() { + Scriptable aScope = evaluateJS("var a=new Set(); a.add(\"a\"); a.add(\"b\"); a"); + + String result = ScriptableUtils.toString(aScope); + assertTrue(result.startsWith("{JS_Set")); + assertTrue(result.contains("\"a\"")); + assertTrue(result.contains("\"b\"")); + assertTrue(result.contains(", ")); + + } + + + private Scriptable evaluateJS(String code) { try { - Context.enter(); - curCtx = Context.getCurrentContext(); - curCtx.setLanguageVersion(Context.VERSION_1_8); + Context curCtx = Context.enter(); + curCtx.setLanguageVersion(Context.VERSION_ES6); ImporterTopLevel importer = new ImporterTopLevel(curCtx); Scriptable tlScope = curCtx.initStandardObjects(importer); - Object scopeObj = curCtx.evaluateString( - tlScope, - "var a={}; a", + Object resultObj = curCtx.evaluateString( + tlScope, code, "", 1, null); - Scriptable aScope = (Scriptable)scopeObj; - String expResult = "{}"; - String result = ScriptableUtils.toString(aScope); - assertEquals(expResult, result); + + return (Scriptable)resultObj; + } finally { Context.exit(); } diff --git a/src/test/resources/broken/requestNonEvents.js b/src/test/resources/broken/requestNonEvents.js new file mode 100644 index 00000000..a9e237fb --- /dev/null +++ b/src/test/resources/broken/requestNonEvents.js @@ -0,0 +1,63 @@ +/* + * The MIT License + * + * Copyright 2020 michael. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +/* global bp, sw */ + +bp.log.info("sw=" + sw); + +if ( sw == 1 ) { + bp.registerBThread("non-event requestor", function(){ + bp.sync({request:bp.Event("NON_EVENT")}); + bp.sync({request:"NON_EVENT"}); + bp.sync({request:"NON_EVENT"}); + bp.sync({request:"NON_EVENT"}); + }); + bp.log.info("sw1 registered."); +} + +if ( sw == 2 ) { + bp.registerBThread(function(){ + bp.sync({request:[bp.Event("OK"),"NON_EVENT"]}); + }); +} + +if ( sw == 3 ) { + bp.registerBThread(function(){ + bp.sync({waitFor:"NON_EVENT_or_SET"}); + }); +} + +if ( sw == 4 ) { + bp.registerBThread(function(){ + bp.sync({block:"NON_EVENT_or_SET"}); + }); +} + +if ( sw == 5 ) { + bp.registerBThread(function(){ + bp.sync({interrupt:"NON_EVENT_or_SET"}); + }); +} +