/interpreter/tags/at2dist170907/test/edu/vub/at/objects/natives/UniformAccessTest.java
Java | 442 lines | 238 code | 48 blank | 156 comment | 0 complexity | cc0faa6d2e83611311d2ef47ee3a2b59 MD5 | raw file
1package edu.vub.at.objects.natives; 2 3import edu.vub.at.AmbientTalkTest; 4import edu.vub.at.actors.ATAsyncMessage; 5import edu.vub.at.exceptions.InterpreterException; 6import edu.vub.at.exceptions.XArityMismatch; 7import edu.vub.at.exceptions.XUnassignableField; 8import edu.vub.at.objects.ATClosure; 9import edu.vub.at.objects.ATField; 10import edu.vub.at.objects.ATObject; 11import edu.vub.at.objects.ATTable; 12import edu.vub.at.objects.coercion.NativeTypeTags; 13import edu.vub.at.objects.mirrors.NativeClosure; 14import edu.vub.at.objects.mirrors.Reflection; 15import edu.vub.at.objects.natives.grammar.AGMessageSend; 16import edu.vub.at.objects.natives.grammar.AGMethodInvocationCreation; 17import edu.vub.at.objects.natives.grammar.AGSymbol; 18 19/** 20 * Unit test class to verify whether the uniform access principle is upheld both for invocations and lexical 21 * function calls. This test covers both the invocation and selection (resp. call and lookup) operations. 22 * 23 * @author smostinc 24 */ 25public class UniformAccessTest extends AmbientTalkTest { 26 27 /** 28 * Used to run this test suite independently of the InterpreterTests Suite 29 */ 30 public static void main(String[] args) { 31 junit.swingui.TestRunner.run(UniformAccessTest.class); 32 } 33 34 /** 35 * Constructor which installs the <tt>withScope: scope do: closure</tt> construct in the 36 * scope in which tests are executed. 37 */ 38 public UniformAccessTest() throws InterpreterException { 39 super(); // sets up the ctx_ properly 40 41 ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("withScope:do:"), atWithScope_Do_); 42 } 43 44 private final NATNumber atThree_ = NATNumber.atValue(3); 45 private final AGSymbol atX_ = AGSymbol.alloc(NATText.atValue("x")); 46 private final AGSymbol atY_ = AGSymbol.alloc(NATText.atValue("y")); 47 private final AGSymbol atZ_ = AGSymbol.alloc(NATText.atValue("z")); 48 private final AGSymbol atM_ = AGSymbol.alloc(NATText.atValue("m")); 49 private final AGSymbol atFooBar_ = AGSymbol.alloc(NATText.atValue("foo:bar:")); 50 51 /** 52 * Auxiliary construct to be used in the test suite to execute a closure within the 53 * scope of a given scope object. This is used to test the semantics of call and 54 * lookup in particularly in combination with native and symbiotic objects. 55 */ 56 private final ATClosure atWithScope_Do_ = new NativeClosure(OBJNil._INSTANCE_) { 57 public ATObject base_apply(ATTable arguments) throws InterpreterException { 58 ATObject closureScope = arguments.base_at(NATNumber.ONE); 59 ATClosure literalClosure = arguments.base_at(NATNumber.atValue(2)).asClosure(); 60 61 return literalClosure.base_method().base_wrap(closureScope, literalClosure.base_context().base_self()) 62 .base_apply(NATTable.EMPTY); 63 } 64 }; 65 66 /** 67 * Tests whether the invoke operation treats fields (such as the length of a table) 68 * and nullary methods (to e.g. compute the absolute value of a number) equally 69 * when invoked upon native data types. 70 * 71 * The invocation is performed both using a field acessing and a canonical invocation 72 * syntax, and illustrates that both are equivalent. 73 */ 74 public void testUniformInvokeOnNatives() throws InterpreterException { 75 // (-1).abs -> (-1).abs() -> 1 76 // [1, 2, 3].length -> [1, 2, 3].length() -> 3 77 evalAndCompareTo("(-1).abs", NATNumber.ONE); 78 evalAndCompareTo("(-1).abs()", NATNumber.ONE); 79 evalAndCompareTo("[1, 2, 3].length", atThree_); 80 evalAndCompareTo("[1, 2, 3].length()", atThree_); 81 } 82 83 /** 84 * Tests whether the call operation treats fields (such as the length of a table) 85 * and nullary methods (to e.g. compute the absolute value of a number) equally 86 * when invoked upon native data types. 87 * 88 * The call is performed both using a field acessing and a canonical invocation 89 * syntax, and illustrates that both are equivalent. 90 */ 91 public void testUniformCallOnNatives() throws InterpreterException { 92 // (-1).abs -> (-1).abs() -> 1 93 // [1, 2, 3].length -> [1, 2, 3].length() -> 3 94 evalAndCompareTo("withScope: (-1) do: { abs }", NATNumber.ONE); 95 evalAndCompareTo("withScope: (-1) do: { abs() }", NATNumber.ONE); 96 evalAndCompareTo("withScope: [1, 2, 3] do: { length }", atThree_); 97 evalAndCompareTo("withScope: [1, 2, 3] do: { length() }", atThree_); 98 } 99 100 /** 101 * Tests the uniform selection of both fields and methods from native data types. The 102 * selection is uniform in the sense that both return closures which can subsequently be 103 * applied. 104 * 105 * When selecting a field, the resulting closure is in fact an implictly created accessor 106 * which provides access to the "current" value of the field, not the one when the field 107 * was selected. 108 */ 109 public void testUniformSelectionOnNatives() throws InterpreterException { 110 // (-1).&abs -> <native impl> <+ apply([]) -> 1 111 // [1, 2, 3].&length -> <native impl> <+ apply([]) -> 3 112 evalAndCompareTo("def abs := (-1).&abs", "<native closure:abs>"); 113 evalAndCompareTo("def len := [1, 2, 3].&length", "<native closure:length>"); 114 evalAndCompareTo("abs()", NATNumber.ONE); 115 evalAndCompareTo("len()", atThree_); 116 117 // selection gives up to date info, not stale one recorded at selection time 118 // first we select an accessor for the receiver exp of an invocation 119 evalAndReturn("def x := `(o.m()); \n" + 120 "def receiver := x.&receiverExpression"); 121 // subsequently assign the receiver expression with a new value 122 evalAndReturn("x.receiverExpression := `object"); 123 // finally assert that the new value is correctly reported 124 evalAndCompareTo("receiver()", AGSymbol.jAlloc("object")); 125 } 126 127 /** 128 * Tests the uniform lookup of both variables and functions in native data types. The 129 * lookup is uniform in the sense that both return closures which can subsequently be 130 * applied. 131 * 132 * When looking up a variable, the resulting closure is in fact an implictly created 133 * accessor which provides access to the "current" value of the variable, not the one 134 * when the accessor was created. 135 */ 136 public void testUniformLookupOnNatives() throws InterpreterException { 137 // (-1).&abs -> <native impl> <+ apply([]) -> 1 138 // [1, 2, 3].&length -> <native impl> <+ apply([]) -> 3 139 evalAndCompareTo("def abs := withScope: (-1) do: { &abs }", "<native closure:abs>"); 140 evalAndCompareTo("def len := withScope: [1, 2, 3] do: { &length }", "<native closure:length>"); 141 evalAndCompareTo("abs()", NATNumber.ONE); 142 evalAndCompareTo("len()", atThree_); 143 144 // lookup gives up to date info, not stale one recorded at lookup time 145 // first we create an accessor for the receiver exp of an invocation 146 evalAndReturn("def x := `(o.m()); \n" + 147 "def receiver := withScope: x do: { &receiverExpression }"); 148 // subsequently assign the receiver expression with a new value 149 evalAndReturn("x.receiverExpression := `object"); 150 // finally assert that the new value is correctly reported 151 evalAndCompareTo("receiver()", AGSymbol.jAlloc("object")); 152 } 153 154 /** 155 * Tests lexical field mutator access. 156 */ 157 public void testLexicalMutatorAccess() throws InterpreterException { 158 evalAndReturn("def testobj := object: { def x := 5; def m() { nil }; def c := { 5 } }"); 159 evalAndCompareTo("withScope: testobj do: { &x:= }", "<native closure:x:=>"); 160 // there is no implicit mutator for nullary methods 161 evalAndTestException("withScope: testobj do: { &m:= }", XUnassignableField.class); 162 evalAndCompareTo("withScope: testobj do: { &c:= }", "<native closure:c:=>"); 163 } 164 165 /** 166 * The correctness of assignments on native data types is verified by the four previous tests in 167 * the course of testing whether the returned accessors return up to date information rather than 168 * stale one recored when the accessor was created. 169 * 170 * This test does not rely on the correct functioning of the accessor but instead manually checks 171 * the output of the implementation-level accessor and is therefore complementary to the previous 172 * tests as it allows determining whether the semantics of assignment or of the created accessor 173 * are incorrect. 174 */ 175 public void testAssignmentOnNatives() throws InterpreterException { 176 AGMethodInvocationCreation msgExp = new AGMethodInvocationCreation( 177 /* selector = */ atM_, 178 /* arguments = */ NATTable.EMPTY, 179 /* annotations = */ NATTable.EMPTY); 180 AGMessageSend sendExp= new AGMessageSend( 181 /* receiver = */ atX_, 182 /* message = */ msgExp); 183 ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("sendExp"), sendExp); 184 185 evalAndReturn("sendExp.receiverExpression := `y"); 186 assertEquals(sendExp.base_receiverExpression(), atY_); 187 } 188 189 /** 190 * The amalgamation of fields and methods by the uniform access principle allows the definition 191 * of setter methods which can be used as if assigning ordinary fields. This test verifies that 192 * the assignment of two fields (one standard field and one implemented with a custom accessor 193 * and mutator pair) can be performed in an identical fashion. 194 */ 195 public void testUniformAssignmentOnObjects() { 196 evalAndReturn( 197 "def time := object: { \n" + 198 " def elapsed := 12752; \n" + 199 "\n" + 200 " def seconds() { (elapsed % 60) }; \n" + 201 " def minutes() { (elapsed /- 60) % 60 }; \n" + 202 " def hours() { (elapsed /- 3600) }; \n" + 203 "\n" + 204 " def seconds:=(newSeconds) { \n" + 205 " elapsed := elapsed - seconds + newSeconds; \n" + 206 " newSeconds \n" + 207 " }; \n" + 208 " def minutes:=(newMinutes) { \n" + 209 " elapsed := elapsed - (minutes * 60) + (newMinutes * 60); \n" + 210 " newMinutes \n" + 211 " }; \n" + 212 " def hours:=(newHours) { \n" + 213 " elapsed := elapsed - (hours * 3600) + (newHours * 3600); \n" + 214 " newHours \n" + 215 " }; \n" + 216 "}"); 217 evalAndCompareTo("time.hours", atThree_); 218 evalAndCompareTo("time.hours := 1", NATNumber.ONE); 219 220 evalAndCompareTo("time.elapsed", "5552"); 221 evalAndCompareTo("time.elapsed := 12752", "12752"); 222 223 evalAndCompareTo("withScope: time do: { hours }", atThree_); 224 evalAndCompareTo("withScope: time do: { hours := 1 }", NATNumber.ONE); 225 226 evalAndCompareTo("withScope: time do: { elapsed }", "5552"); 227 evalAndCompareTo("withScope: time do: { elapsed := 12752 }", "12752"); 228 } 229 230 /** 231 * Tests both field and variable access as well as method invocation and function calls occurring 232 * in the scope of a symbiotic JavaObject wrapper. Both the canonical application and variable access 233 * syntax are tested. 234 * 235 * This test uses both the size method and the elementCount java field to illustrate that both can 236 * be used interchangibly in AmbientTalk. 237 */ 238 public void testUniformAccessOnSymbionts() { 239 // use a variant of vector which has a public field instead of a protected one 240 evalAndReturn("def jVector := jlobby.edu.vub.at.objects.natives.VectorProxy.new()"); 241 242 evalAndCompareTo("jVector.size", NATNumber.ZERO); 243 evalAndCompareTo("jVector.size()", NATNumber.ZERO); 244 evalAndCompareTo("jVector.elementCount", NATNumber.ZERO); 245 evalAndCompareTo("jVector.elementCount()", NATNumber.ZERO); 246 247 evalAndCompareTo("withScope: jVector do: { size }", NATNumber.ZERO); 248 evalAndCompareTo("withScope: jVector do: { size() }", NATNumber.ZERO); 249 evalAndCompareTo("withScope: jVector do: { elementCount }", NATNumber.ZERO); 250 evalAndCompareTo("withScope: jVector do: { elementCount() }", NATNumber.ZERO); 251 } 252 253 /** 254 * Tests both the selection and lookup of both methods and implict field accessors in the scope of 255 * a symbiotic JavaObject wrapper. Both the canonical application and variable access syntax are tested. 256 * 257 */ 258 public void testUniformSelectionOnSymbionts() { 259 evalAndReturn("def jVector := jlobby.edu.vub.at.objects.natives.VectorProxy.new()"); 260 261 evalAndCompareTo( 262 "def selSize := jVector.&size", 263 "<java closure:size>"); 264 evalAndCompareTo( 265 "def lexSize := \n" + 266 "withScope: jVector do: { &size }", 267 "<java closure:size>"); 268 evalAndCompareTo( 269 "def selElementCount := jVector.&elementCount", 270 "<native closure:elementCount>"); 271 evalAndCompareTo( 272 "def lexElementCount := \n" + 273 "withScope: jVector do: { &elementCount }", 274 "<native closure:elementCount>"); 275 276 evalAndReturn("jVector.add( [4, 8, 15, 16, 23, 42] )"); 277 278 evalAndCompareTo("selSize", "<java closure:size>"); 279 evalAndCompareTo("selSize()", NATNumber.ONE); 280 evalAndCompareTo("lexSize", "<java closure:size>"); 281 evalAndCompareTo("lexSize()", NATNumber.ONE); 282 283 evalAndCompareTo("selElementCount", "<native closure:elementCount>"); 284 evalAndCompareTo("selElementCount()", NATNumber.ONE); 285 evalAndCompareTo("lexElementCount", "<native closure:elementCount>"); 286 evalAndCompareTo("lexElementCount()", NATNumber.ONE); 287 } 288 289 /** 290 * Tests whether abstraction can be made over the accessor or a slot, 291 * independent of whether a slot is implemened as a field or as a pair 292 * of methods. 293 */ 294 public void testMutatorSelection() { 295 evalAndReturn("def v; def pair := object: {" + 296 "def x := 1;" + 297 "def y() {v};" + 298 "def y:=(v2) { v := v2; v } }"); 299 300 // test mutator for field x 301 evalAndCompareTo("def xmutator := pair.&x:=", "<native closure:x:=>"); 302 evalAndCompareTo("xmutator(2)", "2"); 303 evalAndTestException("xmutator()", XArityMismatch.class); 304 evalAndCompareTo("pair.x", "2"); 305 306 // test mutator for virtual field y 307 evalAndCompareTo("def ymutator := pair.&y:=", "<closure:y:=>"); 308 evalAndCompareTo("ymutator(2)", "2"); 309 evalAndTestException("ymutator()", XArityMismatch.class); 310 evalAndCompareTo("pair.y", "2"); 311 } 312 313 /** 314 * Tests how invocation, calling, lookup and select interact with 315 * fields, methods and fields bound to closures. Primarly, the uniform 316 * access principle is demonstrated by its ability to abstract over whether 317 * a field is implemented as a genuine field or as a set of methods. 318 */ 319 public void testUniformAccessPrinciple() { 320 evalAndReturn( 321 "def o := object: {" + 322 " def c := { 1 };" + 323 " def x := 2;" + 324 " def m() { 3 };" + 325 " def m:=(v) { 4 }" + 326 "}"); 327 328 // dynamic uniform access 329 evalAndCompareTo("o.x", NATNumber.atValue(2)); 330 evalAndCompareTo("o.m", atThree_); // for methods: o.m == o.m() 331 evalAndCompareTo("o.x()", NATNumber.atValue(2)); 332 evalAndCompareTo("o.m()", atThree_); 333 evalAndCompareTo("o.c", "<closure:lambda>"); // for closures: o.c != o.c() 334 evalAndCompareTo("o.c()", NATNumber.ONE); 335 336 evalAndCompareTo("o.x := 2", "2"); // assigns the field 337 evalAndCompareTo("o.m := 0", "4"); // invokes the mutator 338 evalAndCompareTo("o.c := { 1 }", "<closure:lambda>"); // assigns the field 339 340 evalAndCompareTo("o.&x", "<native closure:x>"); // for fields: & returns accessor 341 evalAndCompareTo("o.&m", "<closure:m>"); // for methods: & returns method closure 342 evalAndCompareTo("o.&c", "<native closure:c>"); // for closures: & returns accessor 343 evalAndCompareTo("o.&x:=", "<native closure:x:=>"); // for fields: & returns mutator 344 evalAndCompareTo("o.&m:=", "<closure:m:=>"); // for methods: & returns method closure 345 evalAndCompareTo("o.&c:=", "<native closure:c:=>"); // for closures: & returns mutator 346 347 // lexical uniform access 348 evalAndCompareTo("withScope: o do: { x }", "2"); 349 evalAndCompareTo("withScope: o do: { m }", "3"); // for methods: m == m() 350 evalAndCompareTo("withScope: o do: { x() }", "2"); 351 evalAndCompareTo("withScope: o do: { m() }", "3"); 352 evalAndCompareTo("withScope: o do: { c }", "<closure:lambda>"); // for closures: c != c() 353 evalAndCompareTo("withScope: o do: { c() }", "1"); 354 355 evalAndCompareTo("withScope: o do: { x := 2 }", "2"); // assigns the field 356 evalAndCompareTo("withScope: o do: { m := 0 }", "4"); // invokes the mutator 357 evalAndCompareTo("withScope: o do: { c := { 1 } }", "<closure:lambda>"); // assigns the field 358 359 evalAndCompareTo("withScope: o do: { &x }", "<native closure:x>"); // for fields: & returns accessor 360 evalAndCompareTo("withScope: o do: { &m }", "<closure:m>"); // for methods: & returns method closure 361 evalAndCompareTo("withScope: o do: { &c }", "<native closure:c>"); // for closures: & returns accessor 362 evalAndCompareTo("withScope: o do: { &x:= }", "<native closure:x:=>"); // for fields: & returns mutator 363 evalAndCompareTo("withScope: o do: { &m:= }", "<closure:m:=>"); // for methods: & returns method closure 364 evalAndCompareTo("withScope: o do: { &c:= }", "<native closure:c:=>"); // for closures: & returns mutator 365 366 } 367 368 /** 369 * Tests whether abstraction can be made over the accessor and mutator 370 * of a slot at the meta-level, independent of whether a slot is implemened 371 * as a field or as a pair of methods. 372 */ 373 public void testUniformAccessViaMirrors() { 374 evalAndReturn("def rre := 42; def cplx := object: {" + 375 "def clofield := { 5 };" + 376 "def im := 1;" + 377 "def re() { rre };" + 378 "def re:=(v) { rre := v; rre+2 }" + 379 "}"); 380 evalAndReturn("def cplxm := reflect: cplx"); 381 382 // test whether selection on mirrors can abstract over fields or methods 383 // or fields containing closures 384 evalAndCompareTo("cplxm.select(cplx, `im)", "<native closure:im>"); 385 evalAndCompareTo("cplxm.select(cplx, `re)", "<closure:re>"); 386 evalAndCompareTo("cplxm.select(cplx, `im:=)", "<native closure:im:=>"); 387 evalAndCompareTo("cplxm.select(cplx, `re:=)", "<closure:re:=>"); 388 evalAndCompareTo("cplxm.select(cplx, `clofield)", "<native closure:clofield>"); 389 390 // test whether explicit invocation on mirrors can abstract over fields 391 // or methods or fields containing closures 392 evalAndCompareTo("cplxm.invoke(cplx, `im, [])", "1"); 393 evalAndCompareTo("cplxm.invoke(cplx, `re, [])", "42"); 394 evalAndCompareTo("cplxm.invoke(cplx, `im:=, [4])", "4"); 395 evalAndCompareTo("cplxm.invoke(cplx, `re:=,[3])", "5"); 396 evalAndCompareTo("cplxm.invoke(cplx, `clofield, [])", "5"); // cplx.clofield() = 5 397 evalAndCompareTo("cplxm.invokeField(cplx, `clofield)", "<closure:lambda>"); // cplx.clofield = <lambda> 398 } 399 400 /** 401 * This test is written following a bug report where the following happened: 402 * <code> 403 * def clo() { 5 } 404 * &clo<-apply([])@FutureMessage 405 * </code> 406 * 407 * The interpreter complained that "apply" cound not be found in "5". Hence, 408 * it applied the closure 'too early' and sent apply to the return value of the 409 * closure instead. 410 * 411 * The cause: the future message annotation caused the actual message 412 * being sent to be a real AmbientTalk object that was coerced into an 413 * {@link ATAsyncMessage}. However, the 414 * {@link Reflection#downInvocation(ATObject, java.lang.reflect.Method, ATObject[])} 415 * method failed to recognize a nullary method invocation as a field access 416 * and hence treated 'msg.receiver' as 'msg.receiver()'. Since 'receiver' 417 * was bound to a closure, the closure was automatically applied, rather 418 * than simply being returned. 419 */ 420 public void testCoercedFieldAccess() throws InterpreterException { 421 // originally, the test was performed on actual asynchronous messages because 422 // they encapsulated their receiver. This is no longer true, hence we 423 // test the same principle (whether downInvocation gets it right) on an ATField instead 424 425 // construct a coerced asynchronous message 426 // ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("AsyncMsg"), NativeTypeTags._ASYNCMSG_); 427 // ATAsyncMessage msg = evalAndReturn("object: { def receiver := { 5 } } taggedAs: [AsyncMsg]").asAsyncMessage(); 428 // rcv should be { 5 }, not 5 429 // ATObject rcv = msg.base_receiver(); 430 431 // construct a coerced field object 432 ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("Field"), NativeTypeTags._FIELD_); 433 ATField fld = evalAndReturn("object: { def readField := { 5 } } taggedAs: [Field]").asField(); 434 // readField should be { 5 }, not 5 435 ATObject val = fld.base_readField(); 436 437 assertFalse(val.meta_isTaggedAs(NativeTypeTags._NUMBER_).asNativeBoolean().javaValue); 438 assertTrue(val.meta_isTaggedAs(NativeTypeTags._CLOSURE_).asNativeBoolean().javaValue); 439 assertEquals(NATNumber.atValue(5), val.asClosure().base_apply(NATTable.EMPTY)); 440 } 441 442}