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