TestSDK.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. const axios = require('axios');
  2. const YAML = require('yaml');
  3. const fs = require('node:fs');
  4. const path_ = require('node:path');
  5. const url = require('node:url');
  6. const https = require('node:https');
  7. const Assert = require('./Assert');
  8. const log_error = require('./log_error');
  9. module.exports = class TestSDK {
  10. constructor (conf, context) {
  11. this.conf = conf;
  12. this.context = context;
  13. this.cwd = `/${conf.username}`;
  14. this.httpsAgent = new https.Agent({
  15. rejectUnauthorized: false
  16. })
  17. const url_origin = new url.URL(conf.url).origin;
  18. this.headers_ = {
  19. 'Origin': url_origin,
  20. 'Authorization': `Bearer ${conf.token}`
  21. };
  22. this.installAPIMethodShorthands_();
  23. this.assert = new Assert();
  24. this.sdks = {};
  25. this.results = [];
  26. this.failCount = 0;
  27. this.caseCount = 0;
  28. this.nameStack = [];
  29. this.packageResults = [];
  30. this.benchmarkResults = [];
  31. }
  32. async get_sdk (name) {
  33. return await this.sdks[name].create();
  34. }
  35. // === test related methods ===
  36. async runTestPackage (testDefinition) {
  37. this.nameStack.push(testDefinition.name);
  38. this.packageResults.push({
  39. name: testDefinition.name,
  40. failCount: 0,
  41. caseCount: 0,
  42. });
  43. const imported = {};
  44. for ( const key of Object.keys(testDefinition.import ?? {}) ) {
  45. imported[key] = this.sdks[key];
  46. }
  47. await testDefinition.do(this, imported);
  48. this.nameStack.pop();
  49. }
  50. async runBenchmark (benchDefinition) {
  51. const strid = '' +
  52. '\x1B[35;1m[bench]\x1B[0m' +
  53. this.nameStack.join(` \x1B[36;1m->\x1B[0m `);
  54. process.stdout.write(strid + ' ... \n');
  55. this.nameStack.push(benchDefinition.name);
  56. let results;
  57. this.benchmarkResults.push(results = {
  58. name: benchDefinition.name,
  59. start: Date.now(),
  60. });
  61. try {
  62. await benchDefinition.do(this);
  63. } catch (e) {
  64. results.error = e;
  65. } finally {
  66. results.end = Date.now();
  67. const dur = results.end - results.start;
  68. process.stdout.write(`...\x1B[32;1m[${dur}]\x1B[0m\n`);
  69. }
  70. }
  71. recordResult (result) {
  72. const pkg = this.packageResults[this.packageResults.length - 1];
  73. this.caseCount++;
  74. pkg.caseCount++;
  75. if ( ! result.success ) {
  76. this.failCount++;
  77. pkg.failCount++;
  78. }
  79. this.results.push(result);
  80. }
  81. async case (id, fn) {
  82. this.nameStack.push(id);
  83. const tabs = Array(this.nameStack.length - 2).fill(' ').join('');
  84. const strid = tabs + this.nameStack.join(` \x1B[36;1m->\x1B[0m `);
  85. process.stdout.write(strid + ' ... \n');
  86. try {
  87. await fn(this.context);
  88. } catch (e) {
  89. process.stdout.write(`${tabs}...\x1B[31;1m[FAIL]\x1B[0m\n`);
  90. this.recordResult({
  91. strid,
  92. e,
  93. success: false,
  94. });
  95. log_error(e);
  96. return;
  97. } finally {
  98. this.nameStack.pop();
  99. }
  100. process.stdout.write(`${tabs}...\x1B[32;1m[PASS]\x1B[0m\n`);
  101. this.recordResult({
  102. strid,
  103. success: true
  104. });
  105. }
  106. quirk (msg) {
  107. console.log(`\x1B[33;1mignoring known quirk: ${msg}\x1B[0m`);
  108. }
  109. // === information display methods ===
  110. printTestResults () {
  111. console.log(`\n\x1B[33;1m=== Test Results ===\x1B[0m`);
  112. let tbl = {};
  113. for ( const pkg of this.packageResults ) {
  114. tbl[pkg.name] = {
  115. passed: pkg.caseCount - pkg.failCount,
  116. failed: pkg.failCount,
  117. total: pkg.caseCount,
  118. }
  119. }
  120. console.table(tbl);
  121. process.stdout.write(`\x1B[36;1m${this.caseCount} tests were run\x1B[0m - `);
  122. if ( this.failCount > 0 ) {
  123. console.log(`\x1B[31;1m✖ ${this.failCount} tests failed!\x1B[0m`);
  124. } else {
  125. console.log(`\x1B[32;1m✔ All tests passed!\x1B[0m`)
  126. }
  127. }
  128. printBenchmarkResults () {
  129. console.log(`\n\x1B[33;1m=== Benchmark Results ===\x1B[0m`);
  130. let tbl = {};
  131. for ( const bench of this.benchmarkResults ) {
  132. tbl[bench.name] = {
  133. time: bench.end - bench.start,
  134. error: bench.error ? bench.error.message : '',
  135. }
  136. }
  137. console.table(tbl);
  138. }
  139. // === path related methods ===
  140. cd (path) {
  141. this.cwd = path_.posix.join(this.cwd, path);
  142. }
  143. resolve (path) {
  144. if ( path.startsWith('$') ) return path;
  145. if ( path.startsWith('/') ) return path;
  146. return path_.posix.join(this.cwd, path);
  147. }
  148. // === API calls ===
  149. installAPIMethodShorthands_ () {
  150. const p = this.resolve.bind(this);
  151. this.read = async path => {
  152. const res = await this.get('read', { path: p(path) });
  153. return res.data;
  154. }
  155. this.mkdir = async (path, opts) => {
  156. const res = await this.post('mkdir', {
  157. path: p(path),
  158. ...(opts ?? {})
  159. });
  160. return res.data;
  161. };
  162. this.write = async (path, bin, params) => {
  163. path = p(path);
  164. params = params ?? {};
  165. let mime = 'text/plain';
  166. if ( params.hasOwnProperty('mime') ) {
  167. mime = params.mime;
  168. delete params.mime;
  169. }
  170. let name = path_.posix.basename(path);
  171. path = path_.posix.dirname(path);
  172. params.path = path;
  173. const res = await this.upload('write', name, mime, bin, params);
  174. return res.data;
  175. }
  176. this.stat = async (path, params) => {
  177. path = p(path);
  178. const res = await this.post('stat', { ...params, path });
  179. return res.data;
  180. }
  181. this.statu = async (uid, params) => {
  182. const res = await this.post('stat', { ...params, uid });
  183. return res.data;
  184. }
  185. this.readdir = async (path, params) => {
  186. path = p(path);
  187. const res = await this.post('readdir', {
  188. ...params,
  189. path
  190. })
  191. return res.data;
  192. }
  193. this.delete = async (path, params) => {
  194. path = p(path);
  195. const res = await this.post('delete', {
  196. ...params,
  197. paths: [path]
  198. });
  199. return res.data;
  200. }
  201. this.move = async (src, dst, params = {}) => {
  202. src = p(src);
  203. dst = p(dst);
  204. const destination = path_.dirname(dst);
  205. const source = src;
  206. const new_name = path_.basename(dst);
  207. console.log('move', { destination, source, new_name });
  208. const res = await this.post('move', {
  209. ...params,
  210. destination,
  211. source,
  212. new_name,
  213. });
  214. return res.data;
  215. }
  216. }
  217. getURL (...path) {
  218. const apiURL = new url.URL(this.conf.url);
  219. apiURL.pathname = path_.posix.join(
  220. apiURL.pathname,
  221. ...path
  222. );
  223. return apiURL.href;
  224. };
  225. // === HTTP methods ===
  226. get (ep, params) {
  227. return axios.request({
  228. httpsAgent: this.httpsAgent,
  229. method: 'get',
  230. url: this.getURL(ep),
  231. params,
  232. headers: {
  233. ...this.headers_
  234. }
  235. });
  236. }
  237. post (ep, params) {
  238. return axios.request({
  239. httpsAgent: this.httpsAgent,
  240. method: 'post',
  241. url: this.getURL(ep),
  242. data: params,
  243. headers: {
  244. ...this.headers_,
  245. 'Content-Type': 'application/json',
  246. }
  247. })
  248. }
  249. upload (ep, name, mime, bin, params) {
  250. const adapt_file = (bin, mime) => {
  251. if ( typeof bin === 'string' ) {
  252. return new Blob([bin], { type: mime });
  253. }
  254. return bin;
  255. };
  256. const fd = new FormData();
  257. for ( const k in params ) fd.append(k, params[k]);
  258. const blob = adapt_file(bin, mime);
  259. fd.append('size', blob.size);
  260. fd.append('file', adapt_file(bin, mime), name)
  261. return axios.request({
  262. httpsAgent: this.httpsAgent,
  263. method: 'post',
  264. url: this.getURL(ep),
  265. data: fd,
  266. headers: {
  267. ...this.headers_,
  268. 'Content-Type': 'multipart/form-data'
  269. },
  270. });
  271. }
  272. async batch (ep, ops, bins) {
  273. const adapt_file = (bin, mime) => {
  274. if ( typeof bin === 'string' ) {
  275. return new Blob([bin], { type: mime });
  276. }
  277. return bin;
  278. };
  279. const fd = new FormData();
  280. fd.append('original_client_socket_id', '');
  281. fd.append('socket_id', '');
  282. fd.append('operation_id', '');
  283. let fileI = 0;
  284. for ( let i=0 ; i < ops.length ; i++ ) {
  285. const op = ops[i];
  286. fd.append('operation', JSON.stringify(op));
  287. }
  288. const files = [];
  289. for ( let i=0 ; i < ops.length ; i++ ) {
  290. const op = ops[i];
  291. if ( op.op === 'mkdir' ) continue;
  292. if ( op.op === 'mktree' ) continue;
  293. let mime = op.mime ?? 'text/plain';
  294. const file = adapt_file(bins[fileI++], mime);
  295. fd.append('fileinfo', JSON.stringify({
  296. size: file.size,
  297. name: op.name,
  298. mime,
  299. }));
  300. files.push({
  301. op, file,
  302. })
  303. delete op.name;
  304. }
  305. for ( const file of files ) {
  306. const { op, file: blob } = file;
  307. fd.append('file', blob, op.name);
  308. }
  309. const res = await axios.request({
  310. httpsAgent: this.httpsAgent,
  311. method: 'post',
  312. url: this.getURL(ep),
  313. data: fd,
  314. headers: {
  315. ...this.headers_,
  316. 'Content-Type': 'multipart/form-data'
  317. },
  318. });
  319. return res.data.results;
  320. }
  321. batch_json (ep, ops, bins) {
  322. return axios.request({
  323. httpsAgent: this.httpsAgent,
  324. method: 'post',
  325. url: this.getURL(ep),
  326. data: ops,
  327. headers: {
  328. ...this.headers_,
  329. 'Content-Type': 'application/json',
  330. },
  331. });
  332. }
  333. }