AsyncTests.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. using System.Collections.Concurrent;
  2. using Jint.Native;
  3. using Jint.Runtime;
  4. using Jint.Tests.Runtime.TestClasses;
  5. namespace Jint.Tests.Runtime;
  6. public class AsyncTests
  7. {
  8. [Fact]
  9. public void AwaitPropagationAgainstPrimitiveValue()
  10. {
  11. var engine = new Engine();
  12. var result = engine.Evaluate("(async ()=>await '1')()");
  13. result = result.UnwrapIfPromise();
  14. Assert.Equal("1", result);
  15. }
  16. [Fact]
  17. public void ShouldTaskConvertedToPromiseInJS()
  18. {
  19. Engine engine = new();
  20. engine.SetValue("callable", Callable);
  21. var result = engine.Evaluate("callable().then(x=>x*2)");
  22. result = result.UnwrapIfPromise();
  23. Assert.Equal(2, result);
  24. static async Task<int> Callable()
  25. {
  26. await Task.Delay(10);
  27. Assert.True(true);
  28. return 1;
  29. }
  30. }
  31. [Fact]
  32. public void ShouldReturnedTaskConvertedToPromiseInJS()
  33. {
  34. Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop);
  35. engine.SetValue("asyncTestClass", new AsyncTestClass());
  36. var result = engine.Evaluate("asyncTestClass.ReturnDelayedTaskAsync().then(x=>x)");
  37. result = result.UnwrapIfPromise();
  38. Assert.Equal(AsyncTestClass.TestString, result);
  39. }
  40. [Fact]
  41. public void ShouldRespectCustomProvidedTimeoutWhenUnwrapping()
  42. {
  43. Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop);
  44. engine.SetValue("asyncTestClass", new AsyncTestClass());
  45. var result = engine.Evaluate("asyncTestClass.ReturnDelayedTaskAsync().then(x=>x)");
  46. var timeout = TimeSpan.FromMilliseconds(1);
  47. var exception = Assert.Throws<PromiseRejectedException>(() => result.UnwrapIfPromise(timeout));
  48. Assert.Equal($"Promise was rejected with value Timeout of {timeout} reached", exception.Message);
  49. }
  50. [Fact]
  51. public void ShouldAwaitUnwrapPromiseWithCustomTimeout()
  52. {
  53. Engine engine = new(options => { options.ExperimentalFeatures = ExperimentalFeature.TaskInterop; options.Constraints.PromiseTimeout = TimeSpan.FromMilliseconds(500); });
  54. engine.SetValue("asyncTestClass", new AsyncTestClass());
  55. engine.Execute("""
  56. async function test() {
  57. return await asyncTestClass.ReturnDelayedTaskAsync();
  58. }
  59. """);
  60. var result = engine.Invoke("test").UnwrapIfPromise();
  61. Assert.Equal(AsyncTestClass.TestString, result);
  62. }
  63. [Fact]
  64. public void ShouldReturnedCompletedTaskConvertedToPromiseInJS()
  65. {
  66. Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop);
  67. engine.SetValue("asyncTestClass", new AsyncTestClass());
  68. var result = engine.Evaluate("asyncTestClass.ReturnCompletedTask().then(x=>x)");
  69. result = result.UnwrapIfPromise();
  70. Assert.Equal(AsyncTestClass.TestString, result);
  71. }
  72. [Fact]
  73. public void ShouldTaskCatchWhenCancelled()
  74. {
  75. Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop);
  76. CancellationTokenSource cancel = new();
  77. cancel.Cancel();
  78. engine.SetValue("token", cancel.Token);
  79. engine.SetValue("callable", Callable);
  80. engine.SetValue("assert", new Action<bool>(Assert.True));
  81. var result = engine.Evaluate("callable(token).then(_ => assert(false)).catch(_ => assert(true))");
  82. result = result.UnwrapIfPromise();
  83. static async Task Callable(CancellationToken token)
  84. {
  85. await Task.FromCanceled(token);
  86. }
  87. }
  88. [Fact]
  89. public void ShouldReturnedTaskCatchWhenCancelled()
  90. {
  91. Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop);
  92. CancellationTokenSource cancel = new();
  93. cancel.Cancel();
  94. engine.SetValue("token", cancel.Token);
  95. engine.SetValue("asyncTestClass", new AsyncTestClass());
  96. engine.SetValue("assert", new Action<bool>(Assert.True));
  97. var result = engine.Evaluate("asyncTestClass.ReturnCancelledTask(token).then(_ => assert(false)).catch(_ => assert(true))");
  98. result = result.UnwrapIfPromise();
  99. }
  100. [Fact]
  101. public void ShouldTaskCatchWhenThrowError()
  102. {
  103. Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop);
  104. engine.SetValue("callable", Callable);
  105. engine.SetValue("assert", new Action<bool>(Assert.True));
  106. var result = engine.Evaluate("callable().then(_ => assert(false)).catch(_ => assert(true))");
  107. static async Task Callable()
  108. {
  109. await Task.Delay(10);
  110. throw new Exception();
  111. }
  112. }
  113. [Fact]
  114. public void ShouldReturnedTaskCatchWhenThrowError()
  115. {
  116. Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop);
  117. engine.SetValue("asyncTestClass", new AsyncTestClass());
  118. engine.SetValue("assert", new Action<bool>(Assert.True));
  119. var result = engine.Evaluate("asyncTestClass.ThrowAfterDelayAsync().then(_ => assert(false)).catch(_ => assert(true))");
  120. result = result.UnwrapIfPromise();
  121. }
  122. [Fact]
  123. public void ShouldTaskAwaitCurrentStack()
  124. {
  125. //https://github.com/sebastienros/jint/issues/514#issuecomment-1507127509
  126. Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop);
  127. AsyncTestClass asyncTestClass = new();
  128. engine.SetValue("myAsyncMethod", new Func<Task>(async () =>
  129. {
  130. await Task.Delay(1000);
  131. asyncTestClass.StringToAppend += "1";
  132. }));
  133. engine.SetValue("mySyncMethod2", new Action(() =>
  134. {
  135. asyncTestClass.StringToAppend += "2";
  136. }));
  137. engine.SetValue("asyncTestClass", asyncTestClass);
  138. engine.Execute("async function hello() {await myAsyncMethod();mySyncMethod2();await asyncTestClass.AddToStringDelayedAsync(\"3\")} hello();");
  139. Assert.Equal("123", asyncTestClass.StringToAppend);
  140. }
  141. #if NETFRAMEWORK == false
  142. [Fact]
  143. public void ShouldValueTaskConvertedToPromiseInJS()
  144. {
  145. Engine engine = new();
  146. engine.SetValue("callable", Callable);
  147. var result = engine.Evaluate("callable().then(x=>x*2)");
  148. result = result.UnwrapIfPromise();
  149. Assert.Equal(2, result);
  150. static async ValueTask<int> Callable()
  151. {
  152. await Task.Delay(10);
  153. Assert.True(true);
  154. return 1;
  155. }
  156. }
  157. [Fact]
  158. public void ShouldValueTaskCatchWhenCancelled()
  159. {
  160. Engine engine = new();
  161. CancellationTokenSource cancel = new();
  162. cancel.Cancel();
  163. engine.SetValue("token", cancel.Token);
  164. engine.SetValue("callable", Callable);
  165. engine.SetValue("assert", new Action<bool>(Assert.True));
  166. var result = engine.Evaluate("callable(token).then(_ => assert(false)).catch(_ => assert(true))");
  167. result = result.UnwrapIfPromise();
  168. static async ValueTask Callable(CancellationToken token)
  169. {
  170. await ValueTask.FromCanceled(token);
  171. }
  172. }
  173. [Fact]
  174. public void ShouldValueTaskCatchWhenThrowError()
  175. {
  176. Engine engine = new();
  177. engine.SetValue("callable", Callable);
  178. engine.SetValue("assert", new Action<bool>(Assert.True));
  179. var result = engine.Evaluate("callable().then(_ => assert(false)).catch(_ => assert(true))");
  180. static async ValueTask Callable()
  181. {
  182. await Task.Delay(10);
  183. throw new Exception();
  184. }
  185. }
  186. [Fact]
  187. public void ShouldValueTaskAwaitCurrentStack()
  188. {
  189. //https://github.com/sebastienros/jint/issues/514#issuecomment-1507127509
  190. Engine engine = new();
  191. string log = "";
  192. engine.SetValue("myAsyncMethod", new Func<ValueTask>(async () =>
  193. {
  194. await Task.Delay(1000);
  195. log += "1";
  196. }));
  197. engine.SetValue("myAsyncMethod2", new Action(() =>
  198. {
  199. log += "2";
  200. }));
  201. engine.Execute("async function hello() {await myAsyncMethod();myAsyncMethod2();} hello();");
  202. Assert.Equal("12", log);
  203. }
  204. #endif
  205. [Fact(Skip = "TODO es6-await https://github.com/sebastienros/jint/issues/1385")]
  206. public void ShouldHaveCorrectOrder()
  207. {
  208. var engine = new Engine();
  209. engine.Evaluate("var log = [];");
  210. const string Script = """
  211. async function foo(name) {
  212. log.push(name + " start");
  213. await log.push(name + " middle");
  214. log.push(name + " end");
  215. }
  216. foo("First");
  217. foo("Second");
  218. """;
  219. engine.Execute(Script);
  220. var log = engine.GetValue("log").AsArray();
  221. string[] expected = [
  222. "First start",
  223. "First middle",
  224. "Second start",
  225. "Second middle",
  226. "First end",
  227. "Second end",
  228. ];
  229. Assert.Equal(expected, log.Select(x => x.AsString()).ToArray());
  230. }
  231. [Fact]
  232. public void ShouldPromiseBeResolved()
  233. {
  234. var log = new List<string>();
  235. Engine engine = new();
  236. engine.SetValue("log", (string str) =>
  237. {
  238. log.Add(str);
  239. });
  240. const string Script = """
  241. async function main() {
  242. return new Promise(function (resolve) {
  243. log('Promise!')
  244. resolve(null)
  245. }).then(function () {
  246. log('Resolved!')
  247. });
  248. }
  249. """;
  250. var result = engine.Execute(Script);
  251. var val = result.GetValue("main");
  252. val.Call().UnwrapIfPromise();
  253. Assert.Equal(2, log.Count);
  254. Assert.Equal("Promise!", log[0]);
  255. Assert.Equal("Resolved!", log[1]);
  256. }
  257. [Fact]
  258. public void ShouldPromiseBeResolved2()
  259. {
  260. Engine engine = new();
  261. engine.SetValue("setTimeout",
  262. (Action action, int ms) =>
  263. {
  264. Task.Delay(ms).ContinueWith(_ => action());
  265. });
  266. const string Script = """
  267. var delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
  268. async function main() {
  269. await delay(100);
  270. return 1;
  271. }
  272. """;
  273. var result = engine.Execute(Script);
  274. var val = result.GetValue("main").Call();
  275. Assert.Equal(1, val.UnwrapIfPromise().AsInteger());
  276. }
  277. [Fact]
  278. public async Task ShouldEventLoopBeThreadSafeWhenCalledConcurrently()
  279. {
  280. const int ParallelCount = 1000;
  281. // [NOTE] perform 5 runs since the bug does not always happen
  282. for (int run = 0; run < 5; run++)
  283. {
  284. var tasks = new List<TaskCompletionSource<object>>();
  285. for (int i = 0; i < ParallelCount; i++)
  286. tasks.Add(new TaskCompletionSource<object>());
  287. for (int i = 0; i < ParallelCount; i++)
  288. {
  289. int taskIdx = i;
  290. _ = Task.Factory.StartNew(() =>
  291. {
  292. try
  293. {
  294. Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop);
  295. const string Script = """
  296. async function main(testObj) {
  297. async function run(i) {
  298. await testObj.Delay(100);
  299. await testObj.Add(`${i}`);
  300. }
  301. const tasks = [];
  302. for (let i = 0; i < 10; i++) {
  303. tasks.push(run(i));
  304. }
  305. for (let i = 0; i < 10; i++) {
  306. await tasks[i];
  307. }
  308. return 1;
  309. }
  310. """;
  311. var result = engine.Execute(Script);
  312. var testObj = JsValue.FromObject(engine, new TestAsyncClass());
  313. var val = result.GetValue("main").Call(testObj);
  314. tasks[taskIdx].SetResult(null);
  315. }
  316. catch (Exception ex)
  317. {
  318. tasks[taskIdx].SetException(ex);
  319. }
  320. }, creationOptions: TaskCreationOptions.LongRunning);
  321. }
  322. await Task.WhenAll(tasks.Select(t => t.Task));
  323. await Task.Delay(100, TestContext.Current.CancellationToken);
  324. }
  325. }
  326. class TestAsyncClass
  327. {
  328. private readonly ConcurrentBag<string> _values = new();
  329. public Task Delay(int ms) => Task.Delay(ms);
  330. public Task Add(string value)
  331. {
  332. _values.Add(value);
  333. return Task.CompletedTask;
  334. }
  335. }
  336. }