/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

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