PageRenderTime 63ms CodeModel.GetById 11ms app.highlight 46ms RepoModel.GetById 1ms app.codeStats 1ms

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

http://ambienttalk.googlecode.com/
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}