-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathDyalogBuild.dyalog
2459 lines (2300 loc) · 133 KB
/
DyalogBuild.dyalog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
:Namespace DyalogBuild ⍝ V 1.87.1
⍝ 2017 04 11 MKrom: initial code
⍝ 2017 05 09 Adam: included in 16.0, upgrade to code standards
⍝ 2017 05 21 MKrom: lowercase Because and Check to prevent breaking exisitng code
⍝ 2017 05 22 Adam: reformat
⍝ 2017 05 23 Adam: uppercased Because and Check and moved Words, reinstate blank lines, make compatible with 15.0
⍝ 2017 06 05 Adam: typo
⍝ 2017 06 15 Adam: TOOLS → EXPERIMENTAL
⍝ 2017 06 18 MKrom: test for missing argument
⍝ 2017 06 23 MKrom: get rid of empty function names
⍝ 2017 06 24 Brian: Allow commented lines in dyalogtest files, drop leading comma in args.tests, exit gracefully if no tests defined
⍝ 2017 06 27 Adam: Add disclaimer to help
⍝ 2017 07 24 MKrom: save exists state
⍝ 2017 12 12 Brian: DBuild - allow SALT "special" locations (e.g. [DYALOG], et al), add 'apl' as loadable type
⍝ 2018 01 12 Brian: DBuild - if the user specifies a folder and there's only a single .dyalogbuild file, use it
⍝ 2018 03 28 MKrom: Add "PROD" command, as EXEC but only executed for production builds
⍝ 2018 03 28 MKrom: Tolerate tab in place of space
⍝ 2018 03 29 MKrom: Also look for .dyalogtest file below WSFOLDER and WSFOLDER/Tests
⍝ 2018 03 29 MKrom: Added "Fail" function
⍝ 2018 04 18 Adam: ]??cmd → ]cmd -??
⍝ 2018 05 01 Adam: add SVN tag
⍝ 2018 05 07 Adam: help text tweak
⍝ 2018 05 17 MKrom: Bugfix: EXEC commands were not being executed in production builds
⍝ 2018 05 19 MKrom: Report errors in result of ]dbuild
⍝ 2018 05 24 Adam: Move to DEVOPS
⍝ 2018 06 27 Adam: Remove "experimental" warning
⍝ 2018 06 28 Adam: Check actual version. Add DATA: path, Format=charvecs(default)/charvec/charmat/json, SetEOL=crlf/etc.
⍝ 2018 09 28 Brian: DTest - Allow wildcards ? and * in test function names (regex will also work)
⍝ 2018 11 13 Adam: help tweak
⍝ 2019 02 01 Adam: help
⍝ 2019 02 13 Brian: allow commented lines in build scripts
⍝ 2019 03 14 MBaas: handle nested msgs
⍝ 2020 01 21 MBaas: mostly backward compatible with v12 (need to sort out ⎕R/⎕S,@)
⍝ 2020 01 22 MBaas: compatibility with Classic
⍝ 2020 01 23 MBaas: took care of ⎕R,⎕S,@
⍝ 2020 01 24 MBaas: DBuild: added switch -halt for "halt on error" as in DTest; fixed bugs found while testing under v12
⍝ 2020 01 27 MBaas: DBuild: target: wsid, save=1, off=1 (and switch -save=1 to override settings from file);]DBuild: added -TestClassic
⍝ 2020 01 28 MBaas: DBuild: -TestClassic=version;bug fixes;doco
⍝ 2020 01 29 MBaas: DBuild: $EnvVar
⍝ 2020 03 23 MBaas: made TestClassic is a simple switch w/o values assigned; fixes dealing with -halt in -save in DBuild;various minor fixes
⍝ 2020 04 03 MBaas: added -clear to DTest to make sure that the ws is ⎕CLEARed before executing tests (simplifies repeated testing)
⍝ 2020 04 06 MBaas: ]DBuild 1.25 executes the content of secret variable ⎕SE.DBuild_postSave after saving the ws
⍝ 2020 04 15 MBaas: ]DTest {file} now loads ALL functions present in folder of {file}, but only execute the test specified in file. (So test may use utils w/o bothering about loading them)
⍝ 2020 04 21 MBaas: ]DTest - timestamp (adds ⎕TS to log messages)
⍝ 2020 04 23 MBaas: ]DTest: renamed -timestamp to -ts; added -timeout
⍝ 2020 04 29 MBaas: ]DTest -order=0|1|"NumVec". Default is random order when executing tests and setups. If tests fail, order will be accessible in *.rng.txt files!
⍝ 2020 05 19 MBaas: colon in arguments of the instructions (i.e. LX/EXEC/PROD/DEFAULTS with pathnames) caused trouble. Fixed.
⍝ 2020 05 20 MBaas: variables with platform info; typos fixed (in Help);]DBuild only creates logfile (with -off) if it found errors
⍝ 2020 07 01 MBaas: ]DTest -init; fixed bugs when ]DBuild used -save Modifier
⍝ 2020 07 23 MBaas: v1.30 ]DTest -off;lots of small fixes & enhancements - see commit message for details
⍝ 2020 07 24 MBaas: some fixes for compatibility with old Dyalog versions
⍝ 2020 08 05 AWS: Avoid calling ⎕USING if on AIX or using a classic interpreter - avoids extraneous errors on status window stream
⍝ 2020 08 21 MBaas: v1.31 ]DTest accepts any # of arguments (can be useful for selection of Tests as in DUI QAs)
⍝ 2021 02 04 MBaas: v1.4 ]DBuild deals with ]LINKed files;fixed various bugs in Build & Test
⍝ 2021 01 10 Adam: v1.32 defer getting .NET Version until needed
⍝ 2021 01 20 MBaas: v1.33 moved assignments into dedicated Init function to avoid running them when UCMD is loaded
⍝ 2021 03 16 MBaas: v1.41 merging of various minor changes, mostly TACIT related
⍝ 2021 03 23 MBaas: v1.43 fixes: Classic compatibility, relative paths for tests & suites
⍝ 2021 03 30 MBaas: v1.44 fixes: more Classic compatibility - missed a few things with 1.43, but now it should be done.
⍝ 2021 04 22 MBaas: v1.45: improved loading of code (from .dyalog + .apln,.aplc,.aplf,.apli,.aplo);various fixes & cleanups
⍝ 2021 04 30 MBaas: v1.46 has a better workaround for saving (no need to go into the session);-save=0 can overwrite the option set with TARGET
⍝ 2021 05 19 MBaas: v1.50 special handling of WS FULL in DTest and DBuild; allows specifying TARGET with .exe or .dll extension;handle multiple TARGET entries per file
⍝ 2021 06 02 MBaas: v1.51: v1.50 did not report ALL errors
⍝ 2021 06 24 MBaas: v1.52: minor details
⍝ 2021 07 26 MBaas: v1.53: fixed VALUE ERROR in ]DBuild
⍝ 2021 07 29 MBaas: v1.60: added switch to -coco to ]DTest to enable testing of Code Coverage (requires version ≥18.0)
⍝ 2021 09 01 MBaas: v1.61: DTest -off=2 to create .log file for failed tests, but not ⎕OFF (useful when called by other functions, as CITA does)
⍝ -testlog also create {basename}.session.log ALWAYS (success or failure) with the entire session output of executing the test, launching a test shows current version of DyalogBuild
⍝ DTest: -trace switch will also trace into setup functions, not only tests.
⍝ Help for DBuild or DTest will also show version number
⍝ 2021 12 17 MBaas, v1.62: new internal variable (available to tests): _isCITA - is set to 1 if running under control of CITA (Continous Integration Tool for APL)
⍝ v1.62: streamlined logging and creation of logfiles (reporting errors and optionally info and warnings, too)
⍝ v1.62: it is also possible to get test results in a .json file (see loglvl): this file also has performance stats and collects various memory related data
⍝ 2022 01 10 MBaas, v1.63: DBuild: ⎕WSID will not be set if save=0 (use save=2 to not save, but set ⎕WSID). "-q" modifier suppresses ALL logging (only logs errors)
⍝ 2022 05 26 MBaas, v1.70: DBuild: DATA: support for .TXT files was mistakenly removed with 1.4 - fixed that. Also: DEFAULTS were never applied to # - now they are. New modifier -target to override TARGET.
⍝ also incompatible change: to import *.APLA use the APL directive! DATA used to support this, but it now strictly reads file content and assigns it.
⍝ Removed code for compatibility with old versions. DBuild/DTest 1.7 requires at least Dyalog v18.0.
⍝ Various little tweaks in DBuild & DTest and its tools (for example, if -halt is used, Check will produce more verbose output & explanation).
⍝ Renamed switch "-coco" to "-coverage"
⍝ Support for "SuccessValue" in .dyalogtest file: in case tests would return boolean result instead of string. (see help for details)
⍝ SuccessValue defines the exact value which test return to indicate "success". Anything else will be interpreted as sign of failure.
⍝ The values for the setup and teardown modifiers are now optional, so you can avoid running any by using the modifier w/o value (so -setup will run NO setups)
⍝ Added Assert for "lighter" tests (details: https://github.com/Dyalog/DBuildTest/wiki/Assert)
⍝ 2022 07 01 MBaas, v1.71: made Log less verbose when msg type is provided;PerfStats now contain "raw" (unformatted) data which makes it easier to analyse
⍝ 2022 07 25 MBaas, v1.72: fixed issues with the workarounds of "0⎕SAVE problem" (refs from ⎕SE to #)
⍝ 2022 07 26 MBaas, v1.73: DBuild: dealt with error if wsid contains invalid path; minor addition to help for -prod flag
⍝ 2022 08 15 MBaas, v1.74: DBuild: addressed #11 (if prod is set, -quiet will default to 1 and -save to 0); shorter log for loading of files to avoid linebreaks;added check for valence of setup/teardown/test functions
⍝ 2022 08 26 MBaas, v1.75: DBuild & DTest: tweaked help texts. DBuild: the mechanism to use config parameters is more robust and supports alternate notations.
⍝ 2022 09 16 MBaas, v1.76: DTest: rearrenged code setup of "ns" for .dyalogest files (##.verbose; setup/test/teardown fns can also ne niladic now; result of setup was not tested against SuccessValue; fixed handling of CodeCoverage_Subject in test suites.
⍝ 2022 10 07 MBaas: v1.77: DTest: CodeCoverage tweaks
⍝ 2023 02 01 MBaas, v1.78: Assert: fixed bug if a failing test is not surrounded by documenting code; result is optional now; error logs also include details of .NET Exceptions
⍝ 2023 02 03 MBaas, v1.79: Assert also shows ⍺ and ⍵ (if their tally is 60 or below)
⍝ 2023 02 04 MBaas, v1.80: _cita._LogStatus ensures its ⍵ is scalar when it is numeric
⍝ 2023 04 28 MBaas, v1.81: Check: additional context info in msgs of failing checks - if -halt is set, we also display the calling line
⍝ 2023 05 10 MBaas, v1.82: DTest now counts and reports the number of "Checks" that were executed (calls of function Check)
⍝ 2023 05 20 MBaas, v1.83: DBuild: the icon-parameter of the target-directive may use "./" to indicate path relative to the location of the .dyalogbuild file
⍝ 2023 07 05 MBaas, v1.84: DBuild: added "nousource" directive and option for TARGET to save a dws w/o source (neccessary when building for Classic & Unicode!)
⍝ 2023 09 25 MBaas, v1.85: DBuild: now supports building StandaloneNativeExe on macOS and Linux as well (from v19.0 onwards). ]DTest -f= supports wildcards * and ?;DTest:RandomVal early exit if ⍵=1; ]DTest with folder argument executes single setup_ fn (previously ignored it)
⍝ 2032 09 27 MBaas, v1.85.1: DTest: fixed bug where using the "-order" with a numeric vector enclosed in " had no effect.
⍝ 2032 09 28 MBaas, v1.85.2: DTest: Version returns a "proper" numeric version no., "SemVer" has a semantic version no.
⍝ 2032 10 02 MBaas, v1.85.3: DTest: on failing tests (or setups) DTest reported SuccessValue as vec when it was scalar
⍝ 2023 10 03 MBaas, v1.85.4: "]cmd -?" shows fully qualified name of UCMD in options for more help
⍝ 2023 11 28 MBaas, v1.85.5: localized vars in SemVer
⍝ 2023 12 13 MBaas, v1.85.6: DyalogBuild accepts parameters enclosed in quotes; DBuild "-p" and "-nosource" weren't working as intended with v19
⍝ 2024 02 14 MBaas, v1.86.0: DTest: revised mechanism to load added CodeCoverage: either from a folder in one of
⍝ the UCMD folders or using CODECOVERAGE_PATH;LogError & Log were spitting out their arguments
⍝ into the session (unless q=0, but sometimes even then) - disabled that as the log should now
⍝ collect all output anyway. Pls. let us know if this a problem for you.
⍝ 2024 06 07 MBaas, v1.87.0: DTest.Check - Check was supposed to display the line of code that called check - this wasn't working and now does.
⍝ Kai has upgraded CodeCoverage to 0.10.6 which now can also process code in #.
⍝ NB: Kai's class is no longer distributed with DTest, but can be downloaded from https://github.com/aplteam/CodeCoverage/releases
⍝ Please also see prev. comment wrt location of the CodeCoverage class. (ofc we're using the latest 0.10.7)
⍝ DTest -? did not mention the -coverag flag. Fixed.
⍝ DTest: Missing setup/teardown-fns did not ⎕STOP when -halt was set
⍝ DTest: it is possible to add an empty "setup:" declaration to .dyalogtest files to avoid running any setup functions
⍝ DTest: logging was in some cases duplicated into the session - I think this is avoided now w/o any loss of information.
⍝ DTest: -clear switch also removes existing LINKs in #
⍝ 2024 08 13 MBaas, v1.87.1: DTest: Check shows more info about data type and shape of arguments when they are not equal
CodeCoverageVersion←'0.10.7'
SuccessValue←''
⍝ does not get in as a var with v19s startup
∇ R←DEBUG
R←⎕SE.SALTUtils.DEBUG ⍝ used for testing to disable error traps ⍝ BTW, m19091 for that being "⎕se" (instead of ⎕SE) even after Edit > Reformat.
∇
:Section Compatibility
⎕IO←1
⎕ML←1
∇ R←GetDOTNETVersion;vers;⎕IO;⎕USING
⍝ R[1] = 0/1/2: 0=nothing, 1=.net Framework, 2=NET CORE
⍝ R[2] = Version (text vector)
⍝ R[3] = Version (identifiable x.y within [2] in numerical form)
⍝ R[4] = Textual description of the framework
⎕IO←1
R←0 '' 0 ''
:If (82=⎕DR' ')∨'AIX'≡3↑⊃'.'⎕WG'APLVersion'
⍝ calls on ⎕USING generate output which is not wanted on AIX or classic interpreters. On Windows or Unicode
⍝ .NET Core may not be installed, so the output is valid. ⎕USING may generate trappable errors in future
⍝ rendering this redundant.
→0
:EndIf
:Trap 0
⎕USING←'System' ''
vers←System.Environment.Version
R[2]←⊂⍕vers
R[3]←vers.(Major+0.1×Minor)
:If 4=⌊R[3] ⍝ a 4 indicates .net Framework!
R[1]←1
:If (⍕vers)≡'4.0.30319.42000' ⍝ .NET 4.6 and higher!
R[4]←⊂Runtime.InteropServices.RuntimeInformation.FrameworkDescription
:ElseIf (10↑⍕vers)≡'4.0.30319.' ⍝ .NET 4, 4.5, 4.5.1, 4.5.2
R[4]←⊂'.NET Framework ',2⊃R
:EndIf
:ElseIf 3.1=R[3] ⍝ .NET CORE
:OrIf 4<R[3]
R[1]←2
⎕USING←'System,System.Runtime.InteropServices.RuntimeInformation'
R[4]←⊂Runtime.InteropServices.RuntimeInformation.FrameworkDescription
:EndIf
:Else
⍝ bad luck, go with the defaults
:EndTrap
∇
∇ {R}←GetTools4CITA args;names
:Access public
⍝ args primarily intended for internal use (giving the ns in which to setup DSL)
⍝ sets up ns "⎕se._cita' and define some tools for writing tests in it (and copy DSL into # or ns passed as argument)
⍝ a few essential ⎕N* Covers and the tools for CITA to write a log and a status file
Init 2
:If 0=⎕SE.⎕NC'_cita'
names←'SetupCompatibilityFns' 'DyaVersion' 'APLVersion' 'isChar' 'Split' 'Init' 'GetDOTNETVersion'
names,←'_Filetime_to_TS' 'Nopen'
names,←'isWin' 'isChar' 'GetCurrentDirectory' 'unixfix' ⍝ needed by these tools etc.
names,←'swise' 'refs' ⍝ useful to deal with WS FULL
names,←⊂'ListPost15'
names,←'base64' 'base64dec' 'base64enc'
'⎕se._cita'⎕NS names
_cita.('⎕se._cita'⎕NS ⎕NL ¯3)
:EndIf
⎕SE._cita.Init 2
:If args≡''
args←'#'
:EndIf
:Trap DEBUG↓0
args ⎕NS'eis' 'base64' 'base64dec' 'base64enc'
:EndTrap
⎕RL←⍬ 2 ⍝ CompCheck: ignore
R←'─── Loaded tools into namespace ⎕se._cita ─── (WA=',(,'CI15'⎕FMT ⎕WA),' bytes) ───'
∇
∇ {sink}←SetupCompatibilityFns
sink←⍬ ⍝ need dummy result here, otherwise getting VALUE ERROR when ⎕FX'ing namespace
eis←{⊆⍵}
table←⍪
ltack←⊣
rtack←⊢
GetNumParam←{⍺←'0' ⋄ ⊃2⊃⎕VFI ⍺ GetParam ⍵} ⍝ Get numeric parameter (0 if not set)
where←⍸
tally←≢ ⍝ CompCheck: ignore - leave it here for compatibility with old tests...
lc←¯1∘⎕C ⍝ lower case ⍝ CompCheck: ignore
uc←1∘⎕C ⍝ upper case ⍝ CompCheck: ignore
GetFilesystemType←{⊃1 ⎕NINFO ⍵} ⍝ 1=Directory, 2=Regular file ⍝ CompCheck: ignore
ListFiles←{⍺←'' ⋄ ⍺ ListPost15 ⍵}
∇
∇ r←GetCurrentDirectory;GCD;GetLastError
⍝ Get Current Directory
:Select APLVersion
:CaseList '*nix' 'Mac'
r←⊃_SH'pwd'
:Case 'Win'
'GCD'⎕NA'I kernel32.C32∣GetCurrentDirectory* I4 >0T'
:If 0≠1⊃r←GCD 256 256
r←2⊃r
:Else
⎕NA'I4 kernel32.C32|GetLastError'
11 ⎕SIGNAL⍨'GetCurrentDirectory error:',⍕GetLastError
:EndIf
:EndSelect
∇
∇ r←{quietly}_SH cmd
:Access public shared
quietly←{6::⍵ ⋄ quietly}0
:If quietly
cmd←cmd,' </dev/null 2>&1'
:EndIf
r←{0::'' ⋄ ⎕SH ⍵}cmd
∇
∇ r←{pattern}ListPost15 path
:If 0=⎕NC'pattern'
:OrIf pattern≡''
pattern←''
:Else
path,←(~path[≢path]∊'\/')/'/'
:EndIf
r←⍉↑0 2 9 1(⎕NINFO ⎕OPT 1)(path,pattern) ⍝ CompCheck: ignore
r[;4]←r[;4]=1
∇
∇ rslt←_Filetime_to_TS filetime;⎕IO
:If 1≠0⊃rslt←FileTimeToLocalFileTime filetime(⎕IO←0)
:OrIf 1≠0⊃rslt←FileTimeToSystemTime(1⊃rslt)0
rslt←0 0 ⍝ if either call failed then zero the time elements
:EndIf
rslt←1 1 0 1 1 1 1 1/1⊃rslt ⍝ remove day of week
∇
∇ Chars←GetText name;nid;signature;nums;sz;b
⍝ Read ANSI or Unicode character file
sz←⎕NSIZE nid←(unixfix name)⎕NTIE 0
signature←⎕NREAD nid 83 3 0
:If signature≡¯17 ¯69 ¯65 ⍝ UTF-8?
nums←⎕NREAD nid 83 sz
Chars←'UTF-8'⎕UCS 256|nums ⍝ Signed ints
:ElseIf ∨/b←(2↑signature)∧.=2 2⍴¯1 ¯2 ¯2 ⍝ Unicode (UTF-16)
Chars←{,⌽(2,⍨2÷⍨⍴⍵)⍴⍵}⍣(ltack/b)⎕NREAD nid 83 sz 2
Chars←'UTF-16'⎕UCS(2*16)|163 ⎕DR Chars
:Else ⍝ ANSI or UTF-8
Chars←{11::⎕UCS ⍵ ⋄ 'UTF-8'⎕UCS ⍵}256|⎕NREAD nid 83 sz 0
:EndIf
⎕NUNTIE nid
∇
∇ vtv←GetVTV name
⍝ Read ANSI or Unicode character file as vector of text vectors
:Trap 0
vtv←{1↓¨(v=n)⊂v←(n←⎕UCS 10),⍵}(GetText name)~⎕UCS 13
:Else ⍝ Classic V12/13 have problems with GetText of this file (as used in dVersion), so if everything failed, this is our last resort
vtv←⎕SE.UnicodeFile.ReadNestedText name
:EndTrap
∇
∇ f←unixfix f;slash;space
⍝ replaces Windows file separator \ with Unix file separator /
⍝ '\ ' is denotes an escaped space under Unix - so don't change those \
⍝ escape any spaces that remain
⍝ this approach is mindnumbingly simple and probably dangerous
⍝ which is why we call unixfix very cautiously
:If (⊂APLVersion)∊'*nix' 'Mac'
⍝ fails on the unlikely (but possible to create on Windows) '\tmp\ myspace.txt'
slash←'\'=f
space←' '=f
((slash>1↓space,0)/f)←'/'
((space>¯1↓0,slash)/f)←⊂'\ '
f←∊f
:EndIf
∇
∇ {names}←{options}LoadCode file_target_mode;target;file;whatWeHave;f1;f2;f3;fl;fls;sep;sf;source;res;mode;larg;ref
⍝ loads code from scriptfile (NB: file points to one existing file, no pattern etc.)
⍝ Options defines SALT options
⍝ file_target_mode: (filename )
⍝ res: nested vector of names that were defined
names←0⍴⊂''
→(0=≢file_target_mode)/0 ⍝ gracefully treatment of empty calls
:If 2=≢file_target_mode ⋄ file_target_mode←file_target_mode,⊂'apl' ⋄ :EndIf
(file target mode)←file_target_mode
:If 0=⎕NC'options'
options←''
:EndIf
:If target≢'' ⍝ if we have a target
:AndIf 9≠⎕NC'target' ⍝ and it's not a ref
:AndIf 0=⎕NC target ⍝ and doesn't even exist yet
target ⎕NS'' ⍝ THEN we create it!
:EndIf
options←' ',options
(f1 f2 f3)←⎕NPARTS file
⍝ filenames may contain wildcards - which isn't so useable with Link.Import.
⍝ So we resolve them and work through the list, processing every file as good as we can
⍝ but List may not be the right tool to do that because it does not give us a filename with extension - so we can't recognize -.apla!
⍝ OTOH, a default DIR lister would not search the SALT libraries etc.
⍝ search the specified file in the source folder and SALT's workdir (emulate SALT.Load here)
sep←'∘',(1+isWin)⊃':' ';' ⍝ separator for those paths...
:For sf :In (⊂f1),sep Split ⎕SE.SALT.Settings'workdir'
:If ~(⊃⌽sf)∊'\/'
sf,←⎕SE.SALT.FS
:EndIf
:If 0<⍬⍴⍴fls←(ListFiles sf,f2,f3)[;1]
:For fl :In fls
:If 'data'≡lc mode
res←1⊃⎕NGET fl 1
names,←⊂fl res
:Else ⍝ mode≡pl
:Select lc 3⊃⎕NPARTS fl
:CaseList '.dyalog' '.aplc' '.aplf' '.apln' '.aplo' '.apli'
:Trap DEBUG↓0 ⍝↓↓↓↓ be sure to pass target as a ref, bad things may happen otherwise ()
:If {(1=≢⍵)∧⊃1↑,⍵}~(⎕NC⍕target)∊0 9 ⍝ deal with name clashes
:AndIf (,'#')≢,⍕target
('target="',(⍕target),'" exists already with ⎕NC=',(⍕⎕NC target),' and is protected')⎕SIGNAL(∨/' -protect'⍷options)/11
⎕EX target
:EndIf
ref←{9=⎕NC'⍵':⍵ ⋄ ⍎⍵}target
:If ∨/'-nolink'⍷options
res←2 ref.⎕FIX 1⊃(⎕NGET ⎕OPT'ContentType' 'APLCode')fl 1
:Else
res←2 ref.⎕FIX'file://',fl
:EndIf
:Else
res←'*** Error ⎕FIXing code in ',fl,': ',NL
res,←⎕DMX.(OSError{⍵,2⌽(×≢⊃⍬⍴2⌽⍺)/'") ("',⊃⍬⍴2⌽⍺}Message{⍵,⍺,⍨': '/⍨×≢⍺}⊃⍬⍴DM,⊂'') ⍝ CompCheck: ignore
:EndTrap
res ⎕SIGNAL('***'≡3↑⍕res)/11
names,←⊂res
:Case '.apla'
:If 9=⎕SE.⎕NC'Link'
:AndIf 3=⎕SE.Link.⎕NC'Import'
:Trap DEBUG↓0
{}⎕SE.Link.Import(⍎target)(fl)
:Else
res←'*** Error executing Link.Import (',target,') ',fl,':'
res,←⎕DMX.(OSError{⍵,2⌽(×≢⊃⍬⍴2⌽⍺)/'") ("',⊃⍬⍴2⌽⍺}Message{⍵,⍺,⍨': '/⍨×≢⍺}⊃⍬⍴DM,⊂'') ⍝ CompCheck: ignore
res ⎕SIGNAL 11
:EndTrap
names,←⊂({2 6::{2 6::⍵ ⋄ ⎕SE.Link.StripCaseCode 2⊃⎕NPARTS ⍵}⍵ ⋄ ⎕SE.Link.U.StripCaseCodePart ⍵}2⊃⎕NPARTS fl) ⍝ CompCheck: ignore
:Else
('*** We need ]LINK to import ',fl)⎕SIGNAL 11
:EndIf
:EndSelect
:EndIf
:EndFor
:EndIf
:If ~0∊⍴names ⍝ if we found any names
:Leave ⍝ do not bother searching SALT's workdirs!
:EndIf
:EndFor
∇
∇ {r}←data Put name
⍝ Write data to file. r=number of bytes written.
:Select |≡data
:CaseList 0 1
:Case 2
data←⊃data
:Case 3
data←∊(⊃data),¨⊂NL
:Else
⎕SIGNAL 11
:EndSelect
data←'UTF-8'⎕UCS data ⍝ get unsigned int so that we can write unicode
data←¯128+256|128∘+data ⍝ convert to signed int
r←data{z←⍺ ⎕NAPPEND(0 ⎕NRESIZE ⍵)83 ⋄ ⎕NUNTIE ⍵ ⋄ z}Nopen name
∇
∇ tn←Nopen name
:Trap 0
tn←name ⎕NCREATE 0
:Else
tn←name ⎕NTIE 0
:EndTrap
∇
∇ R←{vers}∆TestClassic string;avu
⍝ ⎕AVU from Classic 12.1 ↓↓↓↓ ↓↓↓↓
R←''
avu←0 8 10 13 32 12 6 7 27 9 9014 619 37 39 9082 9077 95 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 1 2 175 46 9068 48 49 50 51 52 53 54 55 56 57 3 164 165 36 163 162 8710 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 4 5 253 183 127 9049 193 194 195 199 200 202 203 204 205 206 207 208 210 211 212 213 217 218 219 221 254 227 236 240 242 245 123 8364 125 8867 9015 168 192 196 197 198 9064 201 209 214 216 220 223 224 225 226 228 229 230 231 232 233 234 235 237 238 239 241 91 47 9023 92 9024 60 8804 61 8805 62 8800 8744 8743 45 43 247 215 63 8714 9076 126 8593 8595 9075 9675 42 8968 8970 8711 8728 40 8834 8835 8745 8746 8869 8868 124 59 44 9073 9074 9042 9035 9033 9021 8854 9055 9017 33 9045 9038 9067 9066 8801 8802 243 244 246 248 34 35 30 38 180 9496 9488 9484 9492 9532 9472 9500 9508 9524 9516 9474 64 249 250 251 94 252 96 8739 182 58 9079 191 161 8900 8592 8594 9053 41 93 31 160 167 9109 9054 9059
:If 2=⎕NC'vers'
:Select vers ⍝ use new version
:Case 12
avu←0 8 10 13 32 12 6 7 27 9 9014 619 37 39 9082 9077 95 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 1 2 175 46 9068 48 49 50 51 52 53 54 55 56 57 3 164 165 36 163 162 8710 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 4 5 253 183 127 9049 193 194 195 199 200 202 203 204 205 206 207 208 210 211 212 213 217 218 219 221 254 227 236 240 242 245 123 8364 125 8867 9015 168 192 196 197 198 9064 201 209 214 216 220 223 224 225 226 228 229 230 231 232 233 234 235 237 238 239 241 91 47 9023 92 9024 60 8804 61 8805 62 8800 8744 8743 45 43 247 215 63 8714 9076 126 8593 8595 9075 9675 42 8968 8970 8711 8728 40 8834 8835 8745 8746 8869 8868 124 59 44 9073 9074 9042 9035 9033 9021 8854 9055 9017 33 9045 9038 9067 9066 8801 8802 243 244 246 248 34 35 30 38 8217 9496 9488 9484 9492 9532 9472 9500 9508 9524 9516 9474 64 249 250 251 94 252 8216 8739 182 58 9079 191 161 8900 8592 8594 9053 41 93 31 160 167 9109 9054 9059
:Case 13 ⍝ ⎕AVU from 14.0 onwards (the only difference is rtack which was (can't show here) before )
avu←0 8 10 13 32 12 6 7 27 9 9014 619 37 39 9082 9077 95 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 1 2 175 46 9068 48 49 50 51 52 53 54 55 56 57 3 8866 165 36 163 162 8710 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 4 5 253 183 127 9049 193 194 195 199 200 202 203 204 205 206 207 208 210 211 212 213 217 218 219 221 254 227 236 240 242 245 123 8364 125 8867 9015 168 192 196 197 198 9064 201 209 214 216 220 223 224 225 226 228 229 230 231 232 233 234 235 237 238 239 241 91 47 9023 92 9024 60 8804 61 8805 62 8800 8744 8743 45 43 247 215 63 8714 9076 126 8593 8595 9075 9675 42 8968 8970 8711 8728 40 8834 8835 8745 8746 8869 8868 124 59 44 9073 9074 9042 9035 9033 9021 8854 9055 9017 33 9045 9038 9067 9066 8801 8802 243 244 246 248 34 35 30 38 180 9496 9488 9484 9492 9532 9472 9500 9508 9524 9516 9474 64 249 250 251 94 252 96 8739 182 58 9079 191 161 8900 8592 8594 9053 41 93 31 160 167 9109 9054 9059
:Case 14
avu←0 8 10 13 32 12 6 7 27 9 9014 619 37 39 9082 9077 95 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 1 2 175 46 9068 48 49 50 51 52 53 54 55 56 57 3 8866 165 36 163 162 8710 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 4 5 253 183 127 9049 193 194 195 199 200 202 203 204 205 206 207 208 210 211 212 213 217 218 219 221 254 227 236 240 242 245 123 8364 125 8867 9015 168 192 196 197 198 9064 201 209 214 216 220 223 224 225 226 228 229 230 231 232 233 234 235 237 238 239 241 91 47 9023 92 9024 60 8804 61 8805 62 8800 8744 8743 45 43 247 215 63 8714 9076 126 8593 8595 9075 9675 42 8968 8970 8711 8728 40 8834 8835 8745 8746 8869 8868 124 59 44 9073 9074 9042 9035 9033 9021 8854 9055 9017 33 9045 9038 9067 9066 8801 8802 243 244 246 248 34 35 30 38 180 9496 9488 9484 9492 9532 9472 9500 9508 9524 9516 9474 64 249 250 251 94 252 96 8739 182 58 9079 191 161 8900 8592 8594 9053 41 93 31 160 167 9109 9054 9059
:Else
LogError'Unknow value for ClassicVersion, supported values are 12, 13, 14'
→0
:EndSelect
:EndIf
:If isChar string
R←∪(~(⎕UCS,string)∊avu)/,string
:EndIf
∇
⍝ useful for CITA or DTest when dealing with WS FULL (from dfns):
refs←{ ⍝ Vector of sub space references for ⍵.
⍺←⍬ ⋄ (⍴,⍺)↓⍺{ ⍝ default exclusion list.
⍵∊⍺:⍺ ⍝ already been here: quit.
⍵.(↑∇∘⍎⍨/⌽(⊂⍺∪⍵),↓⎕NL 9) ⍝ recursively traverse any sub spaces.
}⍵ ⍝ for given starting ref.
}
swise←{ ⍝ Space wise
⍺⍺¨refs ⍵ ⍝ Apply to each space
}
⍝─── these functions are removed with v1.7 - but in this release we'll issue a DEPRECATED msg when they are called (so that anyone that uses them becomes aware of needed update...)
qNDELETE←qNEXISTS←qNGET←qMKDIR←qNPARTS←qJSON←qJSONi←qJSONe←qPUT←ListPre15←{((1⊃⎕SI),' is deprecated with DBuildTest 1.7 - please use native functions instead or v1.33 which still supports these')⎕SIGNAL 6}
⍝ need to replace niladic _FindDefine with a tradfn:
⎕fx'_FindDefine' '((1⊃⎕si),'' is deprecated with DBuildTest 1.7 - please use native functions instead or v1.33 which still supports these'')⎕signal 11' ⍝ niladic function...
:endSection Compatibility
:Section ADOC
∇ t←Describe
t←1↓∊(⎕UCS 10),¨{⍵/⍨∧\(⊂'')≢¨⍵}Comments ⎕SRC ⎕THIS ⍝ first block of non-empty comment lines ⍝ CompCheck: ignore
∇
∇ (n v d)←Version;f;s;z
⍝ Version of DBuildTest (3 elems: name version date) with a "proper number" for the version (formatted, though)
(n v d)←SemVer
v←{(2>+\⍵='.')/⍵}v
∇
∇ (n v d)←SemVer;s;f
⍝ Version of DBuildTest (3 elems: name version date) using semantic versioning
s←⎕SRC ⎕THIS
f←Words⊃s ⍝ split first line
n←2⊃f ⍝ ns name
v←'V'~⍨⊃⌽f ⍝ version number
d←1↓∊'-',¨3↑Words{w←⎕VFI¨⍵ ⋄ ⍵⊃⍨⊃{(,⍵)/,⍳⍴⍵}∊(∧/¨3↑¨1⊃¨w)}⌽Comments s ⍝ date (sorry, extra complicated - but getting date from last comment that has one)
∇
Words←{(' '≠⍵){⎕ML←3 ⋄ ⍺⊂⍵}⍵}
Comments←{{1↓¨⍵/⍨∧\'⍝'=⊃∘{(∨\' '≠⍵)/⍵}¨⍵}1↓⍵}
:EndSection ───────────────────────────────────────────────────────────────────────────────────
:Section UTILS
∇ r←APLVersion
:Select 3↑⊃'.'⎕WG'APLVersion'
:CaseList 'Lin' 'AIX' 'Sol'
r←'*nix'
:Case 'Win'
r←'Win'
:Case 'Mac'
r←'Mac'
:Else
∘∘∘ ⍝ unknown version
:EndSelect
∇
∇ r←isWin
r←'Win'≡APLVersion
∇
∇ R←dVersion
⍝ numeric version (maj.min) of DBuildTest (for comparison against the min. version given in the Dyalogtest element of a .dyalogtest)
R←2⊃⎕VFI 2⊃Version
∇
Split←{dlb¨1↓¨(1,⍵∊⍺)⊂(⊃⍺),⍵} ⍝ Split ⍵ on ⍺, and remove leading blanks from each segment
sSplit←{dlb¨1↓¨(1,(0=2|+\⍵='"')∧⍵∊⍺)⊂(⊃⍺),⍵} ⍝ string safe split (does not get confused by stuff enclosed in quotes)
Splitb←{ 1↓¨(1,⍺)⊂'.',⍵} ⍝ Split of ⍵ where ⍺=1 (no dlb)
SplitFirst←{dlb¨1↓¨(1,<\⍵=⍺)⊂⍺,⍵} ⍝ Split ⍵ on first occurence of ⍺, and remove leading blanks from each segment
GetParam←{⍺←'' ⋄ dtb dlb(⌊/names⍳eis ⍵)⊃values,⊂⍺} ⍝ Get value of parameter
⍝unq←{(3>≢⍵)∨~isChar ⍵:⍵ ⋄ z←'""'≡⍵[1,≢⍵] ⋄ z↓(-z)↓⍵}
⍝↑↑↑unquote string (fails if we have a string like '"my long Path", -flag=" and my flag"') - but I don't think that's likely to happend with the current params
⍝↓↓↓ better:
unq←{(3>≢⍵)∨~isChar ⍵:⍵ ⋄ ('^\s*"([^"]*)"'⎕R'\1')⍵}
dlb←{(∨\' '≠⍵)/⍵} ⍝ delete leading blanks
dtb←{(-{⍵⊥⍵}⍵=' ')↓⍵} ⍝ delete trailing blanks (DB)
null←0 ⍝ UCMD switch not specified
whiteout←{w←⍵ ⋄ ((w=⎕UCS 9)/w)←' ' ⋄ w} ⍝ convert whitespace to space
isChar ←{0 2∊⍨10|⎕DR ⍵} ⍝ determine if argument's datatype is character
_hasBitSet←{t←8⍴2 ⋄ 0<+/(t⊤⍺)∧t⊤⍵} ⍝ deal with bit flags (hardcoded maximum is 8)
WIN←⎕SE.SALTUtils.WIN ⍝ running under Windows1
∇ r←∆CSV args;z;file;encoding;coltypes;num
⍝ Primitive ⎕CSV for pre v16
⍝ No validation, no options
:Trap 2 ⍝ Syntax Error if ⎕CSV not available
r←⎕CSV args ⍝ CompCheck: ignore
:Else
(file encoding coltypes)←args
z←1⊃⎕NGET file
z←1↓¨↑{(','=⍵)⊂⍵}¨',',¨z
:If 0≠⍴num←(2=coltypes)/⍳⍴coltypes
z[;num]←{⊃2⊃⎕VFI ⍵}¨z[;num]
:EndIf
r←z
:EndTrap
∇
⍝ from dfns:
rmcm←{ ⍝ APL source with comments removed.
cm←∨\(⍵='⍝')>≠\⍵='''' ⍝ mask of comments.
⎕UCS(cm×32)+(~cm)×⎕UCS ⍵ ⍝ blanks for comments in source matrix.
}
∇ Init lvl;aplv1
⍝ Setup some names with information about the platform we're working on
:If lvl≥0
_Version←2⊃'.'⎕VFI 2⊃'.'⎕WG'APLVersion'
⍝ Version has all the details (major, minor, revision, *)
⍝ whereas DyaVersion is more readable with its simple numeric format
DyaVersion←{2⊃⎕VFI(2>+\'.'=⍵)/⍵}2⊃'.'⎕WG'APLVersion'
:EndIf
:If lvl≥1
:OrIf lvl=¯1
_isClassic←Classic←82=⎕DR' '
_is32bit←~_is64bit←∨/'64'⍷aplv1←1⊃'.'⎕WG'APLVersion'
_OS←3↑aplv1 ⍝ 12.1 does not know it...
(_isWin _isLinux _isAIX _isMacOS _isSolaris)←'Win' 'Lin' 'AIX' 'Mac' 'Sol'∊⊂3↑_OS
_isCITA←~0∊⍴2 ⎕NQ'.' 'GetEnvironment' 'CITATEST'
_DotNet←1⊃GetDOTNETVersion
NL←⎕UCS 10,⍨isWin/13
:EndIf
:If lvl≥2
SetupCompatibilityFns ⍝ dedicated function avoid unneccessary execution of that code when loading the UCMD
:EndIf
∇
∇ r←base64 w
⍝ from dfns workspace
r←{⎕IO ⎕ML←0 1 ⍝ Base64 encoding and decoding as used in MIME.
chars←'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
bits←{,⍉(⍺⍴2)⊤⍵} ⍝ encode each element of ⍵ in ⍺ bits,
⍝ and catenate them all together
part←{((⍴⍵)⍴⍺↑1)⊂⍵} ⍝ partition ⍵ into chunks of length ⍺
0=2|⎕DR ⍵:2∘⊥∘(8∘↑)¨8 part{(-8|⍴⍵)↓⍵}6 bits{(⍵≠64)/⍵}chars⍳⍵
⍝ decode a string into octets
four←{ ⍝ use 4 characters to encode either
8=⍴⍵:'=='∇ ⍵,0 0 0 0 ⍝ 1,
16=⍴⍵:'='∇ ⍵,0 0 ⍝ 2
chars[2∘⊥¨6 part ⍵],⍺ ⍝ or 3 octets of input
}
cats←⊃∘(,/)∘((⊂'')∘,) ⍝ catenate zero or more strings
cats''∘four¨24 part 8 bits ⍵
}w
∇
∇ b64←base64enc txt
b64←base64'UTF-8'⎕UCS txt
∇
∇ txt←base64dec b64
txt←'UTF-8'⎕UCS base64 b64
∇
:EndSection ────────────────────────────────────────────────────────────────────────────────────
:Section TEST "DSL" FUNCTIONS
∇ {r}←l Assert b;cl;cc;t;v;nr;z
nr←1↓⎕NR 2⊃⎕XSI ⍝ drop off header
cl←⎕LC[2]⊃nr ⍝ the current line
:If verbose ⍝ look for "verbose" in current ns or its parent
⎕←cl
:EndIf
→(l Check b)↓r←0
t←nr[(⍳≢nr)∩⎕LC[2]-0 1 ¯1] ⍝ search exactly these 3 lines, avoiding INDEX ERRORs
t←('⍝(.*)'⎕S'\1'⎕OPT('Mode' 'L'))t ⍝ search for text of comments
cc←(¯1+cl⍳'⍝')↑cl
v←''
:If 60≥≢⍕l
:AndIf 60≥≢⍕b
v←((⎕UCS 10),' left ',{'arg = "',(⍕⍵),'", ⎕DR=',(⍕⎕DR ⍵),', rho=',⍕⍴⍵}l),⎕UCS 10
v,←('right ',{'arg = "',(⍕⍵),'", ⎕DR=',(⍕⎕DR ⍵),', rho=',⍕⍴⍵}b),⎕UCS 10
:EndIf
:If ∨/z←cc=⎕AV[60] ⍝ look for right tack as separator between reason & test (not using symbol directly )
:OrIf ∨/z←'IfNot'⍷cc
t←(¯1+⊃##.where z)↑cc
t←(1⊃⎕RSI)⍎t
(t,v)⎕SIGNAL 777
:ElseIf 0=≢t ⍝ no comment found on or around the crashing line
:OrIf ∨/(1⊃t)⍷cl ⍝ don't add comment if it is on the line of the test!
v ⎕SIGNAL 777
:ElseIf 0<≢1⊃t
((1⊃t),v)⎕SIGNAL 777
:Else
v ⎕SIGNAL 777
:EndIf
∇
IfNot←{
r←~⍵
r/⍺
}
∇ r←Test args;TID;timeout;ai;nl;i;quiet
⍝ run some tests from a namespace or a folder
⍝ switches: args.(filter setup teardown verbose)
⍝ result "r" is build in XTest (excute test) as global "r" gets updated. Can't return explicit result in XTest because we're running it in a thread.
Init 2
'Dyalog APL 18.2 or later is required to run DTest'⎕SIGNAL(DyaVersion<18.2)/11
i←quiet←0 ⍝ Clear/Log needs these
Clear args.clear
timeout←0 args.Switch'timeout'
r←''
:If timeout>0
ai←⎕AI[3]+timeout×1000
TID←XTest&args
:While ⎕AI[3]<ai
:AndIf TID∊⎕TNUMS
⎕DL 0.1 ⍝ wait a little bit...
:EndWhile
:If TID∊⎕TNUMS
r,←⊂'*** Test aborted because thread was still running after timeout of ',(⍕timeout),' seconds'
0 ⎕TKILL TID
:EndIf
:Else
XTest args
:EndIf
∇
∇ XTest args;⎕TRAP;start;source;ns;files;f;z;fns;filter;verbose;LOGS;LOGSi;steps;setups;setup;DYALOG;WSFOLDER;suite;halt;m;v;sargs;overwritten;type;TESTSOURCE;extension;repeat;run;quiet;setupok;trace;matches;t;orig;nl∆;LoggedErrors;i;start0;nl;templ;base;WSFULL;msg;en;off;order;ts;timestamp;home;CoCo;r1;r2;tie;tab;ThisTestID;ignore;loglvl;logBase;logFile;log;∆OldLog;file;res;subj;j;pre;rc;mask;path;lib;ucmdf;ccf;tmpName;CodeCoverage
i←quiet←0
⍝ Not used here, but we define them test scripts that need to locate data:
∆OldLog←⎕SE ⎕WG'Log'
DYALOG←2 ⎕NQ'.' 'GetEnvironment' 'DYALOG'
WSFOLDER←⊃⎕NPARTS ⎕WSID
ThisTestID←(,'ZI4,<->,ZI2,<->,ZI2,<->,ZI2,<:>,ZI2,<:>,ZI3'⎕FMT 1 6⍴⎕TS),' *** DTest ',2⊃SemVer
LOGSi←LOGS←3⍴⊂'' ⍝ use distinct variables for initial logs and test logs
(verbose filter halt quiet trace timestamp order)←args.(verbose filter halt quiet trace ts order)
:If (,quiet)≢(,1)
⎕←ThisTestID ⍝ this MUST go into the session because it marks the start of this test (useful to capture session.log later!)
:EndIf
repeat←{~isChar ⍵:⍵ ⋄ ⍬⍴2⊃⎕VFI ⍵}args.repeat
loglvl←{~isChar ⍵:⍵ ⋄ ⍬⍴2⊃⎕VFI ⍵}args.loglvl
order←{~isChar ⍵:⍵ ⋄ ⍬⍴2⊃⎕VFI ⍵}order
off←{~isChar ⍵:⍵ ⋄ ⍬⍴2⊃⎕VFI ⍵}args.off
:If halt
⎕TRAP←0 'S'
:EndIf ⍝ Defeat UCMD trapping
repeat←1⌈repeat
file←''
args.coverage_subj←null
args.coverage_ignore←{¯1↓∊((⊂⍕⍵),¨'.',¨⍵.⎕NL ¯3 ¯4),¨','}⎕THIS
WSFULL←0 ⍝ indicates if we were hit by WS FULL
⎕SE.DTEST_COUNTER_OF_CALLS_TO_CHECK←0
:If 0∊⍴args.Arguments
:AndIf 9≠#.⎕NC source←⊃args.Arguments←,⊂'Tests'
r←'An argument is required - see ]dtest -? for more information.' ⋄ →0
:ElseIf 9=#.⎕NC source←1⊃args.Arguments ⍝ It's a namespace
ns←#⍎source
TESTSOURCE←⊃1 ⎕NPARTS''
base←source
:Else ⍝ Not a namespace
:If ⎕NEXISTS f←source ⍝ Argument is a file
:OrIf ⎕NEXISTS f←source,'.dyalogtest'
:OrIf ⎕NEXISTS f←WSFOLDER,source
:OrIf ⎕NEXISTS f←WSFOLDER,source,'.dyalogtest'
:OrIf ⎕NEXISTS f←WSFOLDER,'Tests/',source
:OrIf ⎕NEXISTS f←WSFOLDER,'Tests/',source,'.dyalogtest'
:OrIf ⎕NEXISTS f←∊1 ⎕NPARTS source ⍝ deal with relative names for folders
:OrIf ⎕NEXISTS f←∊1 ⎕NPARTS source,'.dyalogtest' ⍝ or individual tests
file←f ⍝ assign this variable which is needed by LogError
(TESTSOURCE z extension)←1 ⎕NPARTS f
base←z
'ns'⎕NS'' ⍝ create temporary namespace to run tests in
:If 2=type←GetFilesystemType f ⍝ it's a file
:If '.dyalogtest'≡lc extension ⍝ That's a suite
:If null≡args.suite
args.suite←f
:EndIf
f←¯1↓TESTSOURCE ⋄ type←1 ⍝ Load contents of folder
:Else ⍝ Arg is a source file - load it
:If filter≢null
LogTest'Can''t run test with file argument AND -filter switch!'
LOGSi←LOGS
→FAIL
:EndIf
:Trap (DEBUG∨halt)↓0
filter←∊LoadCode source ns
:If args.tests≡0
args.tests←filter ⍝ transfer into tests, as filtering could be ambigous and we wouldn't want to run more than required...
:EndIf
f←¯1↓TESTSOURCE ⋄ type←1 ⍝ Load contents of folder
:Else
msg←'Error loading test from folder "',source,'"',NL
LogError msg,⎕DMX.(OSError{⍵,2⌽(×≢⊃⍬⍴2⌽⍺)/'") ("',⊃⍬⍴2⌽⍺}Message{⍵,⍺,⍨': '/⍨×≢⍺}⊃⍬⍴DM,⊂'') ⍝ CompCheck: ignore
→endPrep1
:EndTrap
:EndIf
:EndIf
:If 1=type ⍝ deal with directories in f
TESTSOURCE←∊1 ⎕NPARTS f,(~'/\'∊⍨⊃⌽f)/'/' ⍝ use it accordingly! (and be sure it ends with dir sep)'
files←('*.dyalog'ListFiles f)[;1]
files,←('*.aplf'ListFiles f)[;1] ⍝ .aplf extension!
:For f :In files
:Trap (DEBUG∨halt)↓0
LoadCode f ns
:Else
msg←'Error loading code from file "',f,'"'
LogError msg,⎕DMX.(OSError{⍵,2⌽(×≢⊃⍬⍴2⌽⍺)/'") ("',⊃⍬⍴2⌽⍺}Message{⍵,⍺,⍨': '/⍨×≢⍺}⊃⍬⍴DM,⊂'') ⍝ CompCheck: ignore
:EndTrap
:EndFor
⍝:if null≡args.tests
⍝ args.tests←ns.⎕nl¯3
⍝ :endif
:If verbose
0 Log(⍕1↑⍴files),' file',('s'/⍨1<≢files),' loaded from ',source
:EndIf
:If null≡args.suite ⍝ if no suite is given
:If null≡args.setup
nl←ns.⎕NL ¯3
mask←('setup_'⍷↑nl)[;1]
:If 0<+/mask
args.setup←1↓∊' ',¨mask/nl
:EndIf
:If 1<+/mask
:If 2=GetFilesystemType f ⍝ single file given
Log'No -suite nor -setup selected - running test against all setups!'
:Else ⍝ directory
Log'No -suite nor -setup selected - running all tests in "',f,'" against all setups!'
:EndIf
:EndIf
:If ~0∊⍴v←('teardown_'⍷↑nl)[;1]/nl←ns.⎕NL ¯3
args.teardown←¯1↓∊v,¨' '
:EndIf
:EndIf
:EndIf
:EndIf
:Else
:If args.init ⍝ can we init it?
:AndIf ∧/0<∊⍴¨1↑¨(TESTSOURCE z extension)←1 ⎕NPARTS source ⍝ did user give a file spec? then try to create it!
:If ~⎕NEXISTS TESTSOURCE ⍝ does directory exist?
{}3 ⎕MKDIR TESTSOURCE
:EndIf
:If '.dyalogtest'≡lc extension
templ←('DyalogTest : ',2⊃SemVer)'ID :' 'Description:' '' 'Setup :' 'Teardown:' '' 'Test:'
:Else
templ←('r←',z,' dummy;foo')'r←''''' ':If .. Check ..'(' →0 Because ''test failed'' ⋄ :EndIf')
:EndIf
(⊂templ)⎕NPUT source
Log'Initialised ',source
→0
:EndIf
:If halt ⍝ we found an error and need to stop
⎕←'"',source,'" is neither a namespace nor a folder or a .dyalogtest file.'
(⎕LC[1]+1)⎕STOP 1⊃⎕XSI
:EndIf
LogTest'"',source,'" is neither a namespace nor a folder or a .dyalogtest file.'
(TESTSOURCE base)←2↑1 ⎕NPARTS source
LOGSi←LOGS
→FAIL
:EndIf
:EndIf
endPrep1:
:If null≢suite←args.suite ⍝ Is a test suite defined?
⍝ Merge settings
overwritten←⍬
v←LoadTestSuite suite
:If ~1⊃v
LogError'*** error loading suite "',suite,'": ',2⊃v
:Else
sargs←2⊃v
:For v :In (sargs.⎕NL ¯2)∩args.⎕NL ¯2 ⍝ overlap?
:If null≢args⍎v
overwritten,←⊂v
⍎'sargs.',v,'←args.',v
:EndIf
:EndFor
'args'⎕NS sargs ⍝ merge
:If 0≠⍴overwritten
0 Log'*** warning - test suite overridden by modifiers: ',,⍕overwritten
:EndIf
:EndIf
:EndIf
:If args.SuccessValue≢0
SuccessValue←{0::⍵ ⋄ ⍎⍵}args.SuccessValue
:EndIf
SuccessValue←{
'json!'≡⎕C 5↑⍵:∇ ⎕JSON 5↓⍵
'apl!'≡⎕C 4↑⍵:∇⍎4↓⍵
'b64!'≡⎕C 4↑⍵:∇ base64dec 4↓⍵
⍵
}SuccessValue
⍝ Establish test DSL in the namespace
⍝ :If halt=0
⍝ ns.Check←≢ ⍝ CompCheck: ignore
⍝ :Else
'ns'⎕NS'Check'
⍝ :EndIf
'ns'⎕NS'Because' 'Fail' 'IsNotElement' 'RandomVal' 'tally' 'eis' 'Assert' 'IfNot' 'base64' 'base64dec' 'base64enc'
⍝ transfer some status vars into ns
'ns'⎕NS'verbose' 'filter' 'halt' 'quiet' 'trace' 'timestamp' 'order' 'off'
ns.Log←{⍺←{⍵} ⋄ ⍺ ##.LogTest ⍵} ⍝ ⍺←rtack could cause problems with classic...
:If args.tests≢0
orig←fns←(','Split args.tests)~⊂''
nl←ns.⎕NL ¯3
fns←{w←⍵ ⋄ ((w='?')/w)←'.' ⋄ ((w='*')/w)←⊂'.*' ⋄ ∊⍵}¨fns ⍝ replace bare * wildcard with .* to and ? with . make it valid regex
fns←1⌽¨'$^'∘,¨fns ⍝ note ^ is shift-6, not the APL function ∧
t←1
:If 0∊⍴matches←↑fns ⎕S{⍵.(Block PatternNum)}ns.⎕NL ¯3 ⍝ CompCheck: ignore
LogError'*** function(s) not found: ',,⍕t/orig
fns←⍬
:Else
:If ∨/t←~(⍳⍴fns)∊1+∪matches[;2]
LogError'*** function(s) not found: ',,⍕t/orig
:EndIf
fns←∪matches[⍋matches[;2];1]
:EndIf
:Else ⍝ No functions selected - run all named test_*
fns←{⍵⌿⍨(⊂'test_')≡¨5↑¨⍵}ns.⎕NL ¯3
:If 0=≢fns
LogError'*** no functions match pattern "test_*"'
LOGSi←LOGS
→FAIL
:EndIf
:EndIf
filter←{w←⍵ ⋄ ~∨/'?*'∊⍵:⍵ ⋄ ((w='?')/w)←'.' ⋄ ((w='*')/w)←⊂'.*' ⋄ ∊⍵}filter
:If null≢filter
:AndIf 0=≢fns←filter ⎕S'%'⊢fns
LogError'*** no functions match filter "',filter,'"'
LOGSi←LOGS
→FAIL
:EndIf
:If 0<≢args.setup
:AndIf null≢setups←args.setup
setups←' 'Split args.setup
:Else
setups←null
:EndIf
r←'' ⍝ must be global here, it is the result of the calling fn ()
start0←⎕AI[3]
:Select ,order
:Case ,0 ⍝ order=0: random (or reproduce random from file)
order←(('order',⍕≢fns)RandomVal 2⍴≢fns)∩⍳≢fns
:Case ,1
order←⍳≢fns ⍝ 1: sequential
:Else
order←order{(⍺∩⍵),⍵~⍺}⍳≢fns ⍝ numvec: validate and use that order (but make sure every test gets executed!)
:EndSelect
LOGSi←LOGS
:If null≢args.coverage ⍝ if switch is set
:AndIf (1↑1⊃⎕VFI⍕args.coverage)∨1<≢args.coverage ⍝ and we have either numeric value for switch or a longer string
:AndIf 0=⎕NC'CoCo' ⍝ only neccessary if we don't have an instance yet...
:If 0=≢home←2 ⎕NQ #'GetEnvironment' 'DTEST_CODECOVERAGE_PATH' ⍝ no env var set
⍝ we can't yet rely on Tatin (too much reorganization of THIS code), so let's see if we can use it to bring in CodeCoverage (w/o affecting the environment)^
tmpName←'⎕se.t',⍕1+≢'t'⎕SE.⎕NL 2 3 9
z←{0::'' ⋄ ⎕SE.UCMD ⍵}'TATIN.LoadPackages aplteam-CodeCoverage-',CodeCoverageVersion,' ',tmpName ⍝ try to load it with ucmd
:If ⊃'1 package loaded into'⍷z
CodeCoverage←tmpName⍎'CodeCoverage' ⍝ establish CoCo here as a ref to the one we loaded into tmpName
→gotit ⍝ check the message from ucmd
:EndIf
:For path :In ucmdf←(⊃⎕SE.SALTUtils.PATHDEL)(≠⊆⊢)⎕SE.SALT.Set'cmddir' ⍝ search all UCMD folders
:Trap 22
lib←0 1 ⎕NINFO path,'/aplteam-CodeCoverage-',CodeCoverageVersion ⍝ for a subfolder "aplteam-CodeCoverage-" with specific version
:If 0<¯1↑⍴lib
home←1⊃lib
:Leave
:EndIf
:EndTrap
:EndFor
:EndIf
ccf←home∘,¨'/CodeCoverage.aplc' '/APLSource/CodeCoverage.aplc'('/aplteam-CodeCoverage-',CodeCoverageVersion,'/APLSource/CodeCoverage.aplc') ⍝ also look in plausible subfolders
:If 0=≢home
:OrIf ~∨/z←⎕NEXISTS¨ccf
LogError'Unable to find folder with CodeCoverage files for version ',CodeCoverageVersion,', please download it from https://github.com/aplteam/CodeCoverage/releases and save folder "aplteam-CodeCoverage',CodeCoverageVersion,'" in one of the UCMD folders ',(∊' - '∘,¨ucmdf),' or save it anywhere and set DTEST_CODECOVERAGE_PATH appropriately'
setupok←0
→END
:EndIf
ccf←⊃z/ccf
2 ⎕FIX'file://',ccf
⎕←'loaded coco from ',home
gotit:
:EndIf
:For run :In ⍳repeat
:If verbose∧repeat>1
0 Log'run #',(⍕run),' of ',⍕repeat
:EndIf
:For setup :In (,setups)[('setups',⍕≢setups)RandomVal 2⍴≢setups] ⍝ randomize order of setups
steps←0
start←⎕AI[3]
LOGS←3⍴⊂''
:If verbose
:AndIf setup≢null
:AndIf setup≢,1
r,←⊂'For setup = ',setup
:EndIf
:If ~setupok←(⊂f←setup)∊(,1)null
:If 3=ns.⎕NC f ⍝ function is there
:If verbose
0 Log'running setup: ',f
:EndIf
(trace/1)ns.⎕STOP f
:Trap (~halt∨trace)/0
:If 0=1 2⊃ns.⎕AT f ⍝ niladic setup
f LogTest z←ns⍎f
:Else
f LogTest z←(ns⍎f)⍬
:EndIf
setupok←z≡SuccessValue
:Else
msg←'Error executing setup "',f,'": '
msg,←(⎕JSON ⎕OPT'Compact' 0)⎕DMX
:If 90=⎕EN
:Trap 0
msg,'** Exception details: ',⍕⎕EXCEPTION
:EndTrap
:EndIf
LogError msg
setupok←0
:EndTrap
:Else
:If halt
⎕TRAP←0 'S' ⋄ (⎕LC[1]+1)⎕STOP 1⊃⎕XSI ⋄ :EndIf
LogTest'-setup function not found: ',f
setupok←0
:EndIf
:EndIf
→setupok↓END
⍝ after setup, make sure to start CodeCoverage (if modifier is set) - once only...
:If null≢args.coverage ⍝ if switch is set
:AndIf (1↑1⊃⎕VFI⍕args.coverage)∨1<≢args.coverage ⍝ and we have either numeric value for switch or a longer string
:AndIf 0=⎕NC'CoCo' ⍝ only neccessary if we don't have an instance yet...
:If null≡subj←args.coverage_subj
:If 0<≢subj←#.⎕NL ¯9
subj←¯1↓∊(⊂'#.'),¨subj,¨','
:EndIf
subj,←',',⍕ns
:EndIf
CoCo←⎕NEW CodeCoverage(,⊂subj)
CoCo.Info←'Report created by DTest ',(2⊃SemVer),' which was called with these arguments: ',⊃¯2↑⎕SE.Input
:If 1<≢args.coverage
:AndIf (⎕DR' ')=⎕DR args.coverage
:If ∨/'\/'∊args.coverage ⍝ if the argument looks like a filename (superficial test)
CoCo.filename←args.coverage
:Else ⍝ otherwise we assume it is the name of the instance of an already running coverag-analysis
CoCo.filename←(⍎args.coverage).filename
:EndIf
CoCo.NoStop←1
:Else
CoCo.filename←(739⌶0),,'</CoCoDTest_>,ZI4,ZI2,ZI2,ZI2,ZI2,ZI3'⎕FMT 1 6⍴⎕TS
CoCo.NoStop←0
:EndIf
:If 0=≢ignore←args.coverage_ignore
⍝ignore←∊(⊂⍕⎕THIS),¨'.',¨(⎕THIS.⎕NL ¯3 4),¨','
ignore←∊{(⊂⍕⍵),¨'.',¨(⍵.⎕NL ¯3 ¯4),¨','}⎕SE.input.c
:EndIf
ignore,←(((0<≢ignore)∧','≠¯1↑ignore)⍴','),¯1↓∊(⊂(⍕⎕THIS),'.ns.'),¨('Check' 'base64' 'base64dec' 'base64enc' 'Because' 'Fail' 'IsNotElement' 'RandomVal' 'tally' 'eis' 'Log' 'Assert' 'IfNot'),¨','
CoCo.ignore←ignore
CoCo.Start ⍬
:EndIf
:If verbose
:AndIf 1<≢fns