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

http://ambienttalk.googlecode.com/ · Java · 441 lines · 237 code · 48 blank · 156 comment · 0 complexity · 3fa282f2b75f5d98c72656d0c07d6799 MD5 · raw file

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