legacy2luatest.pl 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. #!/usr/bin/env perl
  2. use strict;
  3. use warnings;
  4. use 5.010;
  5. use autodie;
  6. use File::Basename;
  7. use File::Spec::Functions;
  8. sub read_in_file {
  9. my $in_file = $_[0];
  10. # Will contain lines before first STARTTEST
  11. # as Lua comments.
  12. my @description_lines = ();
  13. # Will contain alternating blocks of lines of textual input
  14. # (text between ENDTEST and EOF/next STARTTEST) and test commands
  15. # (commands between STARTTEST and ENDTEST) as Lua code.
  16. my @test_body_lines = ();
  17. # Will contain current command block, i.e. lines
  18. # between STARTTEST and ENDTEST.
  19. my @command_lines = ();
  20. # Will contain current input block, i.e. lines
  21. # between ENDTEST and STARTTEST.
  22. my @input_lines = ();
  23. open my $in_file_handle, '<', $in_file;
  24. use constant EMIT_DESCRIPTION => 0;
  25. use constant EMIT_COMMAND => 1;
  26. use constant EMIT_INPUT => 2;
  27. # Push lines from current input and
  28. # command blocks into @test_body_lines
  29. # in the correct order.
  30. sub end_input {
  31. my $input_lines = $_[0];
  32. my $command_lines = $_[1];
  33. my $test_body_lines = $_[2];
  34. # If there are input lines, wrap with an `insert`
  35. # command and add before the previous command block.
  36. if (@{$input_lines}) {
  37. my $last_input_line = pop @{$input_lines};
  38. unshift @{$command_lines}, '';
  39. unshift @{$command_lines}, $last_input_line . ']=])';
  40. unshift @{$command_lines}, @{$input_lines};
  41. unshift @{$command_lines}, "insert([=[";
  42. @{$input_lines} = ();
  43. }
  44. # Output remaining command lines.
  45. push @{$test_body_lines}, @{$command_lines};
  46. @{$command_lines} = ();
  47. }
  48. sub format_comment {
  49. # Handle empty comments.
  50. if (/^$/) {
  51. return '';
  52. }
  53. # Capitalize first character and emit as Lua comment.
  54. my $comment = '-- ' . ucfirst $_;
  55. # Add trailing dot if not already there.
  56. $comment .= '.' unless $comment =~ /\.$/;
  57. return $comment;
  58. }
  59. my %states = (
  60. # Add test description to @description_lines.
  61. EMIT_DESCRIPTION() => sub {
  62. if (/^STARTTEST/) {
  63. return EMIT_COMMAND;
  64. }
  65. # If not an empty line, emit as Lua comment.
  66. if (!/^$/) {
  67. # Remove modeline
  68. s/vim:.*set f\w+=vim//g;
  69. # Remove trailing ":"
  70. s/\s*:\s*$//g;
  71. push @description_lines, '-- ' . $_;
  72. }
  73. return EMIT_DESCRIPTION;
  74. },
  75. # Add test commands to @command_lines.
  76. EMIT_COMMAND() => sub {
  77. if (/^ENDTEST/) {
  78. return EMIT_INPUT;
  79. }
  80. # If line starts with ':"', emit a comment.
  81. if (/^:"/) {
  82. # Remove Vim comment prefix.
  83. s/^:"\s*//;
  84. push @command_lines, format_comment $_;
  85. return EMIT_COMMAND;
  86. }
  87. # Extract possible inline comment.
  88. if (/^[^"]*"[^"]*$/) {
  89. # Remove command part and prepended whitespace.
  90. s/^(.*?)\s*"\s*//;
  91. push @command_lines, format_comment $_;
  92. # Set implicit variable to command without comment.
  93. $_ = $1;
  94. }
  95. # Only continue if remaining command is not empty.
  96. if (!/^:?\s*$/) {
  97. # Replace terminal escape characters with <esc>.
  98. s/\e/<esc>/g;
  99. my $startstr = "'";
  100. my $endstr = "'";
  101. # If line contains single quotes or backslashes, use double
  102. # square brackets to wrap string.
  103. if (/'/ || /\\/) {
  104. # If the line contains a closing square bracket,
  105. # wrap it with [=[...]=].
  106. if (/\]/) {
  107. $startstr = '[=[';
  108. $endstr = ']=]';
  109. } else {
  110. $startstr = '[[';
  111. $endstr = ']]';
  112. }
  113. }
  114. # Emit 'feed' if not a search ('/') or ex (':') command.
  115. if (!/^\// && !/^:/) {
  116. # If command does not end with <esc>, insert trailing <cr>.
  117. my $command = 'feed(' . $startstr . $_;
  118. $command .= '<cr>' unless /<esc>$/;
  119. $command .= $endstr . ')';
  120. push @command_lines, $command;
  121. } else {
  122. # Remove prepending ':'.
  123. s/^://;
  124. push @command_lines, 'execute(' . $startstr . $_ . $endstr . ')';
  125. }
  126. }
  127. return EMIT_COMMAND;
  128. },
  129. # Add input to @input_lines.
  130. EMIT_INPUT() => sub {
  131. if (/^STARTTEST/) {
  132. end_input \@input_lines, \@command_lines, \@test_body_lines;
  133. return EMIT_COMMAND;
  134. }
  135. # Skip initial lines if they are empty.
  136. if (@input_lines or !/^$/) {
  137. push @input_lines, ' ' . $_;
  138. }
  139. return EMIT_INPUT;
  140. },
  141. );
  142. my $state = EMIT_DESCRIPTION;
  143. while (<$in_file_handle>) {
  144. # Remove trailing newline character and process line.
  145. chomp;
  146. $state = $states{$state}->($_);
  147. }
  148. # If not all lines have been processed yet,
  149. # do it now.
  150. end_input \@input_lines, \@command_lines, \@test_body_lines;
  151. close $in_file_handle;
  152. return (\@description_lines, \@test_body_lines);
  153. }
  154. sub read_ok_file {
  155. my $ok_file = $_[0];
  156. my @assertions = ();
  157. if (-f $ok_file) {
  158. push @assertions, '';
  159. push @assertions, "-- Assert buffer contents.";
  160. push @assertions, "expect([=[";
  161. open my $ok_file_handle, '<', $ok_file;
  162. while (<$ok_file_handle>) {
  163. # Remove trailing newline character and process line.
  164. chomp;
  165. push @assertions, ' ' . $_;
  166. }
  167. close $ok_file_handle;
  168. $assertions[-1] .= "]=])";
  169. }
  170. return \@assertions;
  171. }
  172. my $legacy_testfile = $ARGV[0];
  173. my $out_dir = $ARGV[1];
  174. if ($#ARGV != 1) {
  175. say "Convert a legacy Vim test to a Neovim lua spec.";
  176. say '';
  177. say "Usage: $0 legacy-testfile output-directory";
  178. say '';
  179. say "legacy-testfile: Path to .in or .ok file.";
  180. say "output-directory: Directory where Lua spec will be saved to.";
  181. say '';
  182. say "Note: Only works reliably for fairly simple tests.";
  183. say " Manual adjustments to generated spec files are required.";
  184. exit 1;
  185. }
  186. my @legacy_suffixes = ('.in', '.ok');
  187. my ($base_name, $base_path, $suffix) = fileparse($legacy_testfile, @legacy_suffixes);
  188. my $in_file = catfile($base_path, $base_name . '.in');
  189. my $ok_file = catfile($base_path, $base_name . '.ok');
  190. # Remove leading 'test'.
  191. my $test_name = $base_name;
  192. $test_name =~ s/^test_?//;
  193. my $spec_file = do {
  194. if ($test_name =~ /^([0-9]+)/) {
  195. catfile($out_dir, sprintf('%03d', $1) . '_spec.lua')
  196. } else {
  197. catfile($out_dir, $test_name . '_spec.lua')
  198. }
  199. };
  200. if (! -f $in_file) {
  201. say "Test input file $in_file not found.";
  202. exit 2;
  203. }
  204. if (! -d $out_dir) {
  205. say "Output directory $out_dir does not exist.";
  206. exit 3;
  207. }
  208. if (-f $spec_file) {
  209. say "Output file $spec_file already exists.";
  210. print "Overwrite (Y/n)? ";
  211. my $input = <STDIN>;
  212. chomp($input);
  213. unless ($input =~ /^y|Y/) {
  214. say "Aborting.";
  215. exit 4;
  216. }
  217. }
  218. # Read .in and .ok files.
  219. my ($description_lines, $test_body_lines) = read_in_file $in_file;
  220. my $assertion_lines = read_ok_file $ok_file;
  221. # Append assertions to test body.
  222. push @{$test_body_lines}, @{$assertion_lines} if @{$assertion_lines};
  223. # Write spec file.
  224. open my $spec_file_handle, ">", $spec_file;
  225. print $spec_file_handle <<"EOS";
  226. @{[join "\n", @{$description_lines}]}
  227. local helpers = require('test.functional.helpers')
  228. local feed, insert, source = helpers.feed, helpers.insert, helpers.source
  229. local clear, execute, expect = helpers.clear, helpers.execute, helpers.expect
  230. describe('$test_name', function()
  231. before_each(clear)
  232. it('is working', function()
  233. @{[join "\n", map { /^$/ ? '' : ' ' . $_ } @{$test_body_lines}]}
  234. end)
  235. end)
  236. EOS
  237. close $spec_file_handle;
  238. say "Written to $spec_file."