PageRenderTime 59ms CodeModel.GetById 13ms app.highlight 39ms RepoModel.GetById 2ms app.codeStats 0ms

/interpreter/tags/at2dist220411/test/edu/vub/at/objects/natives/UniformAccessTest.java

http://ambienttalk.googlecode.com/
Java | 417 lines | 225 code | 46 blank | 146 comment | 0 complexity | bb5e97512a845c2d6f423c84b932a21f 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", "<closure:abs>");
112		evalAndCompareTo("def len := [1, 2, 3].&length", "<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 an object attribute
118		evalAndReturn("def x := object: { def val := 0 }; \n" +
119				      "def attribute := x.&val");
120		// subsequently assign the attribute to a new value
121		evalAndReturn("x.val := 1");
122		// finally assert that the new value is correctly reported
123		evalAndCompareTo("attribute()", NATNumber.ONE);
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 }", "<closure:abs>");
139		evalAndCompareTo("def len := withScope: [1, 2, 3] do: { &length }", "<closure:length>");
140		evalAndCompareTo("abs()", NATNumber.ONE);
141		evalAndCompareTo("len()", atThree_);
142		
143		// selection gives up to date info, not stale one recorded at selection time
144		// first we select an accessor for an object attribute
145		evalAndReturn("def x := object: { def val := 0 }; \n" +
146				      "def attribute := withScope: x do: { &val }");
147		// subsequently assign the attribute to a new value
148		evalAndReturn("x.val := 1");
149		// finally assert that the new value is correctly reported
150		evalAndCompareTo("attribute()", NATNumber.ONE);
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 amalgamation of fields and methods by the uniform access principle allows the definition
166	 * of setter methods which can be used as if assigning ordinary fields. This test verifies that
167	 * the assignment of two fields (one standard field and one implemented with a custom accessor
168	 * and mutator pair) can be performed in an identical fashion.
169	 */
170	public void testUniformAssignmentOnObjects() {
171		evalAndReturn(
172				"def time := object: { \n" +
173				"  def elapsed := 12752; \n" +
174				"\n" +
175				"  def seconds() { (elapsed %  60) }; \n" +
176				"  def minutes() { (elapsed /- 60) % 60 }; \n" +
177				"  def hours()   { (elapsed /- 3600) }; \n" +
178				"\n" +
179				"  def seconds:=(newSeconds) { \n" +
180				"    elapsed := elapsed - seconds + newSeconds; \n" +
181				"    newSeconds \n" +
182				"  }; \n" +
183				"  def minutes:=(newMinutes) { \n" +
184				"    elapsed := elapsed - (minutes * 60) + (newMinutes * 60); \n" +
185				"    newMinutes \n" +
186				"  }; \n" +
187				"  def hours:=(newHours) {  \n" +
188				"    elapsed := elapsed - (hours * 3600) + (newHours * 3600); \n" +
189				"    newHours \n" +
190				"  }; \n" +
191				"}");
192		evalAndCompareTo("time.hours", atThree_);
193		evalAndCompareTo("time.hours := 1", NATNumber.ONE);
194		
195		evalAndCompareTo("time.elapsed", "5552");
196		evalAndCompareTo("time.elapsed := 12752", "12752");
197		
198		evalAndCompareTo("withScope: time do: { hours }", atThree_);
199		evalAndCompareTo("withScope: time do: { hours := 1 }", NATNumber.ONE);
200		
201		evalAndCompareTo("withScope: time do: { elapsed }", "5552");
202		evalAndCompareTo("withScope: time do: { elapsed := 12752 }", "12752");
203	}
204	
205	/**
206	 * Tests both field and variable access as well as method invocation and function calls occurring
207	 * in the scope of a symbiotic JavaObject wrapper. Both the canonical application and variable access 
208	 * syntax are tested.
209	 * 
210	 * This test uses both the size method and the elementCount java field to illustrate that both can
211	 * be used interchangibly in AmbientTalk.
212	 */
213	public void testUniformAccessOnSymbionts() {
214		// use a variant of vector which has a public field instead of a protected one
215		evalAndReturn("def jVector := jlobby.edu.vub.at.objects.natives.VectorProxy.new()");
216		
217		evalAndCompareTo("jVector.size", NATNumber.ZERO);
218		evalAndCompareTo("jVector.size()", NATNumber.ZERO);
219		evalAndCompareTo("jVector.elementCount", NATNumber.ZERO);
220		evalAndCompareTo("jVector.elementCount()", NATNumber.ZERO);
221		
222		evalAndCompareTo("withScope: jVector do: { size }", NATNumber.ZERO);
223		evalAndCompareTo("withScope: jVector do: { size() }", NATNumber.ZERO);
224		evalAndCompareTo("withScope: jVector do: { elementCount }", NATNumber.ZERO);
225		evalAndCompareTo("withScope: jVector do: { elementCount() }", NATNumber.ZERO);
226	}
227	
228	/**
229	 * Tests both the selection and lookup of both methods and implict field accessors in the scope of 
230	 * a symbiotic JavaObject wrapper. Both the canonical application and variable access syntax are tested.
231	 * 
232	 */
233	public void testUniformSelectionOnSymbionts() {
234		evalAndReturn("def jVector := jlobby.edu.vub.at.objects.natives.VectorProxy.new()");
235		
236		evalAndCompareTo(
237				"def selSize := jVector.&size", 
238				"<java closure:size>");
239		evalAndCompareTo(
240				"def lexSize := \n" +
241				  "withScope: jVector do: { &size }", 
242				"<java closure:size>");
243		evalAndCompareTo(
244				"def selElementCount := jVector.&elementCount", 
245				"<native closure:elementCount>");
246		evalAndCompareTo(
247				"def lexElementCount := \n" +
248				  "withScope: jVector do: { &elementCount }", 
249				"<native closure:elementCount>");
250		
251		evalAndReturn("jVector.add( [4, 8, 15, 16, 23, 42] )");
252		
253		evalAndCompareTo("selSize",   "<java closure:size>");
254		evalAndCompareTo("selSize()", NATNumber.ONE);
255		evalAndCompareTo("lexSize",   "<java closure:size>");
256		evalAndCompareTo("lexSize()", NATNumber.ONE);
257
258		evalAndCompareTo("selElementCount",   "<native closure:elementCount>");
259		evalAndCompareTo("selElementCount()", NATNumber.ONE);
260		evalAndCompareTo("lexElementCount",   "<native closure:elementCount>");
261		evalAndCompareTo("lexElementCount()", NATNumber.ONE);	
262	}
263	
264	/**
265	 * Tests whether abstraction can be made over the accessor or a slot,
266	 * independent of whether a slot is implemened as a field or as a pair
267	 * of methods.
268	 */
269	public void testMutatorSelection() {
270		evalAndReturn("def v; def pair := object: {" +
271				"def x := 1;" +
272				"def y() {v};" +
273				"def y:=(v2) { v := v2; v } }");
274		
275		// test mutator for field x
276		evalAndCompareTo("def xmutator := pair.&x:=", "<native closure:x:=>");
277		evalAndCompareTo("xmutator(2)", "2");
278		evalAndTestException("xmutator()", XArityMismatch.class);
279		evalAndCompareTo("pair.x", "2");
280		
281		// test mutator for virtual field y
282		evalAndCompareTo("def ymutator := pair.&y:=", "<closure:y:=>");
283		evalAndCompareTo("ymutator(2)", "2");
284		evalAndTestException("ymutator()", XArityMismatch.class);
285		evalAndCompareTo("pair.y", "2");
286	}
287	
288	/**
289	 * Tests how invocation, calling, lookup and select interact with
290	 * fields, methods and fields bound to closures. Primarly, the uniform
291	 * access principle is demonstrated by its ability to abstract over whether
292	 * a field is implemented as a genuine field or as a set of methods.
293	 */
294	public void testUniformAccessPrinciple() {
295		evalAndReturn(
296				"def o := object: {" +
297				"  def c := { 1 };" +
298				"  def x := 2;" +
299				"  def m() { 3 };" +
300				"  def m:=(v) { 4 }" +
301				"}");
302		
303		// dynamic uniform access
304		evalAndCompareTo("o.x", NATNumber.atValue(2));
305		evalAndCompareTo("o.m", atThree_); // for methods: o.m == o.m()
306		evalAndCompareTo("o.x()", NATNumber.atValue(2));
307		evalAndCompareTo("o.m()", atThree_);
308		evalAndCompareTo("o.c", "<closure:lambda>"); // for closures: o.c != o.c()
309		evalAndCompareTo("o.c()", NATNumber.ONE);
310		
311		evalAndCompareTo("o.x := 2", "2"); // assigns the field
312		evalAndCompareTo("o.m := 0", "4"); // invokes the mutator
313		evalAndCompareTo("o.c := { 1 }", "<closure:lambda>"); // assigns the field
314		
315		evalAndCompareTo("o.&x", "<native closure:x>"); // for fields: & returns accessor
316		evalAndCompareTo("o.&m", "<closure:m>"); // for methods: & returns method closure
317		evalAndCompareTo("o.&c", "<native closure:c>"); // for closures: & returns accessor
318		evalAndCompareTo("o.&x:=", "<native closure:x:=>"); // for fields: & returns mutator
319		evalAndCompareTo("o.&m:=", "<closure:m:=>"); // for methods: & returns method closure
320		evalAndCompareTo("o.&c:=", "<native closure:c:=>"); // for closures: & returns mutator
321		
322		// lexical uniform access
323		evalAndCompareTo("withScope: o do: { x }", "2");
324		evalAndCompareTo("withScope: o do: { m }", "3"); // for methods: m == m()
325		evalAndCompareTo("withScope: o do: { x() }", "2");
326		evalAndCompareTo("withScope: o do: { m() }", "3");
327		evalAndCompareTo("withScope: o do: { c }", "<closure:lambda>"); // for closures: c != c()
328		evalAndCompareTo("withScope: o do: { c() }", "1");
329		
330		evalAndCompareTo("withScope: o do: { x := 2 }", "2"); // assigns the field
331		evalAndCompareTo("withScope: o do: { m := 0 }", "4"); // invokes the mutator
332		evalAndCompareTo("withScope: o do: { c := { 1 } }", "<closure:lambda>"); // assigns the field
333		
334		evalAndCompareTo("withScope: o do: { &x }", "<native closure:x>"); // for fields: & returns accessor
335		evalAndCompareTo("withScope: o do: { &m }", "<closure:m>"); // for methods: & returns method closure
336		evalAndCompareTo("withScope: o do: { &c }", "<native closure:c>"); // for closures: & returns accessor
337		evalAndCompareTo("withScope: o do: { &x:= }", "<native closure:x:=>"); // for fields: & returns mutator
338		evalAndCompareTo("withScope: o do: { &m:= }", "<closure:m:=>"); // for methods: & returns method closure
339		evalAndCompareTo("withScope: o do: { &c:= }", "<native closure:c:=>"); // for closures: & returns mutator
340		
341	}
342
343	/**
344	 * Tests whether abstraction can be made over the accessor and mutator
345	 * of a slot at the meta-level, independent of whether a slot is implemened
346	 * as a field or as a pair of methods.
347	 */
348	public void testUniformAccessViaMirrors() {
349		evalAndReturn("def rre := 42; def cplx := object: {" +
350				"def clofield := { 5 };" +
351				"def im := 1;" +
352				"def re() { rre };" +
353				"def re:=(v) { rre := v; rre+2 }" +
354				"}");
355		evalAndReturn("def cplxm := reflect: cplx");
356		
357		// test whether selection on mirrors can abstract over fields or methods
358		// or fields containing closures
359		evalAndCompareTo("cplxm.select(cplx, `im)", "<native closure:im>");
360		evalAndCompareTo("cplxm.select(cplx, `re)", "<closure:re>");
361		evalAndCompareTo("cplxm.select(cplx, `im:=)", "<native closure:im:=>");
362		evalAndCompareTo("cplxm.select(cplx, `re:=)", "<closure:re:=>");
363		evalAndCompareTo("cplxm.select(cplx, `clofield)", "<native closure:clofield>");
364		
365		// test whether explicit invocation on mirrors can abstract over fields
366		// or methods or fields containing closures
367		evalAndCompareTo("cplxm.invoke(cplx, .im())", "1");
368		evalAndCompareTo("cplxm.invoke(cplx, .re())", "42");
369		evalAndCompareTo("cplxm.invoke(cplx, `(.#(`im:=)(4)))", "4"); // cplxm.invoke(cplx, .im:=(4)) but parser does not support this (yet)
370		evalAndCompareTo("cplxm.invoke(cplx, `(.#(`re:=)(3)))", "5");
371		evalAndCompareTo("cplxm.invoke(cplx, .clofield())", "5"); // cplx.clofield() = 5
372		evalAndCompareTo("cplxm.invokeField(cplx, `clofield)", "<closure:lambda>"); // cplx.clofield = <lambda>
373	}
374	
375	/**
376	 * This test is written following a bug report where the following happened:
377	 * <code>
378	 * def clo() { 5 }
379	 * &clo<-apply([])@FutureMessage
380	 * </code>
381	 * 
382	 * The interpreter complained that "apply" cound not be found in "5". Hence,
383	 * it applied the closure 'too early' and sent apply to the return value of the
384	 * closure instead.
385	 * 
386	 * The cause: the future message annotation caused the actual message
387	 * being sent to be a real AmbientTalk object that was coerced into an
388	 * {@link ATAsyncMessage}. However, the
389	 * {@link Reflection#downInvocation(ATObject, java.lang.reflect.Method, ATObject[])}
390	 * method failed to recognize a nullary method invocation as a field access
391	 * and hence treated 'msg.receiver' as 'msg.receiver()'. Since 'receiver'
392	 * was bound to a closure, the closure was automatically applied, rather
393	 * than simply being returned.
394	 */
395	public void testCoercedFieldAccess() throws InterpreterException {
396		// originally, the test was performed on actual asynchronous messages because
397		// they encapsulated their receiver. This is no longer true, hence we
398		// test the same principle (whether downInvocation gets it right) on an ATField instead
399		
400		// construct a coerced asynchronous message
401		// ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("AsyncMsg"), NativeTypeTags._ASYNCMSG_);
402		// ATAsyncMessage msg = evalAndReturn("object: { def receiver := { 5 } } taggedAs: [AsyncMsg]").asAsyncMessage();
403		// rcv should be { 5 }, not 5
404		// ATObject rcv = msg.base_receiver();
405		
406		// construct a coerced field object
407		ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("Field"), NativeTypeTags._FIELD_);
408		ATField fld = evalAndReturn("object: { def readField := { 5 } } taggedAs: [Field]").asField();
409		// readField should be { 5 }, not 5
410		ATObject val = fld.base_readField();
411		
412		assertFalse(val.meta_isTaggedAs(NativeTypeTags._NUMBER_).asNativeBoolean().javaValue);
413		assertTrue(val.meta_isTaggedAs(NativeTypeTags._CLOSURE_).asNativeBoolean().javaValue);
414		assertEquals(NATNumber.atValue(5), val.asClosure().base_apply(NATTable.EMPTY));
415	}
416	
417}