radius 0.7.4 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +8 -0
  3. data/CONTRIBUTORS +2 -0
  4. data/Gemfile +16 -2
  5. data/README.rdoc +2 -6
  6. data/Rakefile +10 -1
  7. data/coverage/assets/{0.7.1 → 0.10.2}/application.css +52 -363
  8. data/coverage/assets/{0.7.1 → 0.10.2}/application.js +1153 -72
  9. data/coverage/assets/0.10.2/colorbox/border.png +0 -0
  10. data/coverage/assets/0.10.2/colorbox/controls.png +0 -0
  11. data/coverage/assets/0.10.2/colorbox/loading.gif +0 -0
  12. data/coverage/assets/0.10.2/colorbox/loading_background.png +0 -0
  13. data/coverage/assets/0.13.1/DataTables-1.10.20/images/sort_asc.png +0 -0
  14. data/coverage/assets/0.13.1/DataTables-1.10.20/images/sort_asc_disabled.png +0 -0
  15. data/coverage/assets/0.13.1/DataTables-1.10.20/images/sort_both.png +0 -0
  16. data/coverage/assets/0.13.1/DataTables-1.10.20/images/sort_desc.png +0 -0
  17. data/coverage/assets/0.13.1/DataTables-1.10.20/images/sort_desc_disabled.png +0 -0
  18. data/coverage/assets/0.13.1/application.css +1 -0
  19. data/coverage/assets/0.13.1/application.js +7 -0
  20. data/coverage/assets/0.13.1/colorbox/border.png +0 -0
  21. data/coverage/assets/0.13.1/colorbox/controls.png +0 -0
  22. data/coverage/assets/0.13.1/colorbox/loading.gif +0 -0
  23. data/coverage/assets/0.13.1/colorbox/loading_background.png +0 -0
  24. data/coverage/assets/0.13.1/favicon_green.png +0 -0
  25. data/coverage/assets/0.13.1/favicon_red.png +0 -0
  26. data/coverage/assets/0.13.1/favicon_yellow.png +0 -0
  27. data/coverage/assets/0.13.1/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  28. data/coverage/assets/0.13.1/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  29. data/coverage/assets/0.13.1/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  30. data/coverage/assets/0.13.1/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  31. data/coverage/assets/0.13.1/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  32. data/coverage/assets/0.13.1/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  33. data/coverage/assets/0.13.1/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  34. data/coverage/assets/0.13.1/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  35. data/coverage/assets/0.13.1/images/ui-icons_222222_256x240.png +0 -0
  36. data/coverage/assets/0.13.1/images/ui-icons_2e83ff_256x240.png +0 -0
  37. data/coverage/assets/0.13.1/images/ui-icons_454545_256x240.png +0 -0
  38. data/coverage/assets/0.13.1/images/ui-icons_888888_256x240.png +0 -0
  39. data/coverage/assets/0.13.1/images/ui-icons_cd0a0a_256x240.png +0 -0
  40. data/coverage/assets/0.13.1/loading.gif +0 -0
  41. data/coverage/assets/0.13.1/magnify.png +0 -0
  42. data/lib/radius/context.rb +2 -1
  43. data/lib/radius/delegating_open_struct.rb +15 -19
  44. data/lib/radius/ord_string.rb +6 -8
  45. data/lib/radius/parser/JavaScanner.class +0 -0
  46. data/lib/radius/parser/JavaScanner.java +211 -161
  47. data/lib/radius/parser/JavaScanner.rl +36 -7
  48. data/lib/radius/parser/scanner.rb +2 -2
  49. data/lib/radius/parser.rb +69 -40
  50. data/lib/radius/utility.rb +5 -14
  51. data/lib/radius/version.rb +1 -1
  52. data/lib/radius.rb +11 -11
  53. data/pkg/radius-0.8.0.gem +0 -0
  54. data/radius.gemspec +12 -4
  55. data/test/context_test.rb +13 -5
  56. data/test/multithreaded_test.rb +60 -43
  57. data/test/ord_string_test.rb +3 -3
  58. data/test/parser_test.rb +11 -3
  59. data/test/quickstart_test.rb +2 -2
  60. data/test/squiggle_test.rb +3 -3
  61. data/test/test_helper.rb +21 -26
  62. data/test/utility_test.rb +3 -3
  63. metadata +86 -70
  64. data/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
  65. data/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
  66. data/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
  67. data/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
  68. data/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
  69. data/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
  70. data/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
  71. data/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
  72. data/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
  73. data/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
  74. data/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
  75. data/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
  76. data/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
  77. data/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
  78. data/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
  79. data/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
  80. data/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
  81. data/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
  82. data/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
  83. data/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
  84. data/pkg/radius-0.7.0.prerelease.gem +0 -0
  85. /data/coverage/assets/{0.7.1 → 0.10.2}/favicon_green.png +0 -0
  86. /data/coverage/assets/{0.7.1 → 0.10.2}/favicon_red.png +0 -0
  87. /data/coverage/assets/{0.7.1 → 0.10.2}/favicon_yellow.png +0 -0
  88. /data/coverage/assets/{0.7.1 → 0.10.2}/loading.gif +0 -0
  89. /data/coverage/assets/{0.7.1 → 0.10.2}/magnify.png +0 -0
  90. /data/coverage/assets/{0.7.1 → 0.10.2}/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  91. /data/coverage/assets/{0.7.1 → 0.10.2}/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  92. /data/coverage/assets/{0.7.1 → 0.10.2}/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  93. /data/coverage/assets/{0.7.1 → 0.10.2}/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  94. /data/coverage/assets/{0.7.1 → 0.10.2}/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  95. /data/coverage/assets/{0.7.1 → 0.10.2}/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  96. /data/coverage/assets/{0.7.1 → 0.10.2}/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  97. /data/coverage/assets/{0.7.1 → 0.10.2}/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  98. /data/coverage/assets/{0.7.1 → 0.10.2}/smoothness/images/ui-icons_222222_256x240.png +0 -0
  99. /data/coverage/assets/{0.7.1 → 0.10.2}/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  100. /data/coverage/assets/{0.7.1 → 0.10.2}/smoothness/images/ui-icons_454545_256x240.png +0 -0
  101. /data/coverage/assets/{0.7.1 → 0.10.2}/smoothness/images/ui-icons_888888_256x240.png +0 -0
  102. /data/coverage/assets/{0.7.1 → 0.10.2}/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
@@ -3,20 +3,37 @@
3
3
 
4
4
  action _prefix { mark_pfx = p; }
5
5
  action prefix {
6
- prefix = input.substring(mark_pfx, p);
6
+ prefix = String.valueOf(input.substring(mark_pfx, p));
7
7
  }
8
8
  action _check_prefix {
9
9
  if ( !prefix.equals(tag_prefix) ) {
10
- // have to manually add ':' / Sep
11
- // pass the text through & reset state
12
- pass_through(input.substring(tagstart, p) + ":");
10
+ // Pass through the entire tag markup as text
11
+ pass_through(input.substring(tagstart, p + 1));
12
+ // Reset all state
13
13
  prefix = "";
14
+ name = "";
15
+ attributes = RubyHash.newHash(runtime);
16
+ flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
17
+ tagstart = p + 1;
14
18
  fgoto main;
15
19
  }
16
20
  }
17
21
 
18
22
  action _starttag { mark_stg = p; }
19
- action starttag { name = input.substring(mark_stg, p); }
23
+ action starttag {
24
+ name = String.valueOf(input.substring(mark_stg, p));
25
+ if (name == null || name.trim().isEmpty()) {
26
+ // Pass through the entire tag markup as text
27
+ pass_through(input.substring(tagstart, p + 1));
28
+ // Reset all state
29
+ prefix = "";
30
+ name = "";
31
+ attributes = RubyHash.newHash(runtime);
32
+ flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
33
+ tagstart = p + 1;
34
+ fgoto main;
35
+ }
36
+ }
20
37
  action _attr { mark_attr = p; }
21
38
  action attr {
22
39
  attributes.op_aset(
@@ -40,7 +57,7 @@
40
57
  # words
41
58
  PrefixChar = [\-A-Za-z0-9._?] ;
42
59
  NameChar = [\-A-Za-z0-9._:?] ;
43
- TagName = NameChar+ >_starttag %starttag;
60
+ TagName = ([\-A-Za-z0-9._?]+ (':' [\-A-Za-z0-9._?]+)*) >_starttag %starttag;
44
61
  Prefix = PrefixChar+ >_prefix %prefix;
45
62
  Open = "<";
46
63
  Sep = ":" >_check_prefix;
@@ -111,11 +128,23 @@ public class JavaScanner {
111
128
  }
112
129
 
113
130
  void tag(String prefix, String name, RubyHash attr, RubySymbol flavor) {
131
+ // Validate both prefix and name
132
+ if ((prefix == null || prefix.trim().isEmpty()) &&
133
+ (name == null || name.trim().isEmpty())) {
134
+ pass_through("<");
135
+ return;
136
+ }
137
+
138
+ if (name == null || name.trim().isEmpty()) {
139
+ pass_through("<" + prefix + ":");
140
+ return;
141
+ }
142
+
114
143
  RubyHash tag = RubyHash.newHash(runtime);
115
144
  tag.op_aset(
116
145
  runtime.getCurrentContext(),
117
146
  RubySymbol.newSymbol(runtime, "prefix"),
118
- RubyString.newString(runtime, prefix)
147
+ RubyString.newString(runtime, prefix != null ? prefix : "")
119
148
  );
120
149
  tag.op_aset(
121
150
  runtime.getCurrentContext(),
@@ -4,7 +4,7 @@ module Radius
4
4
  # The regular expression used to find (1) opening and self-enclosed tag names, (2) self-enclosing trailing slash,
5
5
  # (3) attributes and (4) closing tag
6
6
  def scanner_regex(prefix = nil)
7
- %r{<#{prefix}:([-\w:]+?)(\s+(?:\w+\s*=\s*(?:"[^"]*?"|'[^']*?')\s*)*|)(\/?)>|<\/#{prefix}:([-\w:]+?)\s*>}
7
+ %r{<#{prefix}:([-\w:]+?)(\s+(?:[-\w]+\s*=\s*(?:"[^"]*?"|'[^']*?')\s*)*|)(\/?)>|<\/#{prefix}:([-\w:]+?)\s*>}
8
8
  end
9
9
 
10
10
  # Parses a given string and returns an array of nodes.
@@ -47,7 +47,7 @@ module Radius
47
47
 
48
48
  def parse_attributes(text) # :nodoc:
49
49
  attr = {}
50
- re = /(\w+?)\s*=\s*('|")(.*?)\2/
50
+ re = /([-\w]+?)\s*=\s*('|")(.*?)\2/
51
51
  while md = re.match(text)
52
52
  attr[$1] = $3
53
53
  text = md.post_match
data/lib/radius/parser.rb CHANGED
@@ -17,63 +17,92 @@ module Radius
17
17
 
18
18
  # Creates a new parser object initialized with a Context.
19
19
  def initialize(context = Context.new, options = {})
20
- if context.kind_of?(Hash) and options.empty?
21
- options, context = context, (context[:context] || context['context'])
22
- end
23
- options = Utility.symbolize_keys(options)
24
- self.context = context ? context.dup : Context.new
25
- self.tag_prefix = options[:tag_prefix] || 'radius'
26
- self.scanner = options[:scanner] || default_scanner
20
+ context, options = normalize_initialization_params(context, options)
21
+ @context = context ? context.dup : Context.new
22
+ @tag_prefix = options[:tag_prefix] || 'radius'
23
+ @scanner = options[:scanner] || default_scanner
24
+ @stack = nil # Pre-initialize stack
27
25
  end
28
26
 
29
27
  # Parses string for tags, expands them, and returns the result.
30
28
  def parse(string)
31
- @stack = [ ParseContainerTag.new { |t| Utility.array_to_s(t.contents) } ]
32
- tokenize(string)
33
- stack_up
29
+ @stack = [create_root_container]
30
+ process_tokens(scanner.operate(tag_prefix, string))
34
31
  @stack.last.to_s
35
32
  end
36
33
 
37
- protected
38
- # Convert the string into a list of text blocks and scanners (tokens)
39
- def tokenize(string)
40
- @tokens = scanner.operate(tag_prefix, string)
34
+ private
35
+
36
+ def normalize_initialization_params(context, options)
37
+ return [context['context'] || context[:context], context] if context.is_a?(Hash) && options.empty?
38
+ [context, Utility.symbolize_keys(options)]
41
39
  end
42
-
43
- def stack_up
44
- @tokens.each do |t|
45
- if t.is_a? String
46
- @stack.last.contents << t
47
- next
48
- end
49
- case t[:flavor]
50
- when :open
51
- @stack.push(ParseContainerTag.new(t[:name], t[:attrs]))
52
- when :self
53
- @stack.last.contents << ParseTag.new {@context.render_tag(t[:name], t[:attrs])}
54
- when :close
55
- popped = @stack.pop
56
- raise WrongEndTagError.new(popped.name, t[:name], @stack) if popped.name != t[:name]
57
- popped.on_parse { |b| @context.render_tag(popped.name, popped.attributes) { Utility.array_to_s(b.contents) } }
58
- @stack.last.contents << popped
59
- when :tasteless
60
- raise TastelessTagError.new(t, @stack)
61
- else
62
- raise UndefinedFlavorError.new(t, @stack)
63
- end
40
+
41
+ def create_root_container
42
+ ParseContainerTag.new { |t| Utility.array_to_s(t.contents) }
43
+ end
44
+
45
+ def process_tokens(tokens)
46
+ tokens.each { |token| process_token(token) }
47
+ validate_final_stack
48
+ end
49
+
50
+ def process_token(token)
51
+ return @stack.last.contents << token if token.is_a?(String)
52
+
53
+ case token[:flavor]
54
+ when :open then handle_open_tag(token)
55
+ when :self then handle_self_tag(token)
56
+ when :close then handle_close_tag(token)
57
+ when :tasteless then raise TastelessTagError.new(token, @stack)
58
+ else raise UndefinedFlavorError.new(token, @stack)
64
59
  end
60
+ end
61
+
62
+ def handle_open_tag(token)
63
+ @stack.push(ParseContainerTag.new(token[:name], token[:attrs]))
64
+ end
65
+
66
+ def handle_self_tag(token)
67
+ @stack.last.contents << ParseTag.new { @context.render_tag(token[:name], token[:attrs]) }
68
+ end
69
+
70
+ def handle_close_tag(token)
71
+ popped = @stack.pop
72
+ validate_tag_match(popped, token[:name])
73
+ wrap_and_push_tag(popped)
74
+ end
75
+
76
+ def validate_tag_match(popped, name)
77
+ raise WrongEndTagError.new(popped.name, name, @stack) if popped.name != name
78
+ end
79
+
80
+ def wrap_and_push_tag(tag)
81
+ tag.on_parse { |b| @context.render_tag(tag.name, tag.attributes) { Utility.array_to_s(b.contents) } }
82
+ @stack.last.contents << tag
83
+ end
84
+
85
+ def validate_final_stack
65
86
  raise MissingEndTagError.new(@stack.last.name, @stack) if @stack.length != 1
66
87
  end
67
88
 
68
89
  def default_scanner
69
90
  if RUBY_PLATFORM == 'java'
70
- require 'java'
71
- require 'radius/parser/java_scanner.jar'
72
- ::Radius.send(:include_package, 'radius.parser')
73
- Radius::JavaScanner.new(JRuby.runtime)
91
+ load_java_scanner
74
92
  else
75
93
  Radius::Scanner.new
76
94
  end
77
95
  end
96
+
97
+ def load_java_scanner
98
+ if Gem::Version.new(JRUBY_VERSION) >= Gem::Version.new('9.3')
99
+ require 'jruby'
100
+ else
101
+ require 'java'
102
+ end
103
+ require 'radius/parser/java_scanner.jar'
104
+ ::Radius.send(:include_package, 'radius.parser')
105
+ Radius::JavaScanner.new(JRuby.runtime)
106
+ end
78
107
  end
79
108
  end
@@ -22,22 +22,13 @@ module Radius
22
22
  end
23
23
 
24
24
  def self.camelize(underscored_string)
25
- string = ''
26
- underscored_string.split('_').each { |part| string << part.capitalize }
27
- string
25
+ underscored_string.split('_').map(&:capitalize).join
28
26
  end
29
27
 
30
- if RUBY_VERSION[0,3] == '1.8'
31
- def self.array_to_s(c)
32
- c.to_s
33
- end
34
- else
35
- def self.array_to_s(c)
36
- c.map do |x|
37
- res = (x.is_a?(Array) ? array_to_s(x) : x.to_s)
38
- (res.frozen? ? res.dup : res).force_encoding(Encoding::UTF_8)
39
- end.join
40
- end
28
+ def self.array_to_s(c)
29
+ +c.map do |x|
30
+ x.is_a?(Array) ? array_to_s(x) : x.to_s
31
+ end.join
41
32
  end
42
33
  end
43
34
  end
@@ -1,5 +1,5 @@
1
1
  module Radius #:nodoc:
2
- VERSION = "0.7.4"
2
+ VERSION = "0.8.0"
3
3
  def self.version
4
4
  VERSION
5
5
  end
data/lib/radius.rb CHANGED
@@ -1,11 +1,11 @@
1
- require 'radius/version'
2
- require 'radius/error'
3
- require 'radius/tag_definitions'
4
- require 'radius/delegating_open_struct'
5
- require 'radius/tag_binding'
6
- require 'radius/context'
7
- require 'radius/parse_tag'
8
- require 'radius/ord_string'
9
- require 'radius/parser/scanner'
10
- require 'radius/parser'
11
- require 'radius/utility'
1
+ require_relative "radius/version"
2
+ require_relative "radius/error"
3
+ require_relative "radius/tag_definitions"
4
+ require_relative "radius/delegating_open_struct"
5
+ require_relative "radius/tag_binding"
6
+ require_relative "radius/context"
7
+ require_relative "radius/parse_tag"
8
+ require_relative "radius/ord_string"
9
+ require_relative "radius/parser/scanner"
10
+ require_relative "radius/parser"
11
+ require_relative "radius/utility"
Binary file
data/radius.gemspec CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  require File.join(File.dirname(__FILE__), 'lib', 'radius', 'version')
4
4
  Gem::Specification.new do |s|
5
- s.name = %q{radius}
5
+ s.name = "radius"
6
6
  s.version = ::Radius.version
7
+ s.licenses = ["MIT"]
7
8
 
8
- s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
9
- s.authors = [%q{John W. Long (me@johnwlong.com)}, %q{David Chelimsky (dchelimsky@gmail.com)}, %q{Bryce Kerley (bkerley@brycekerley.net)}]
9
+ s.required_ruby_version = ">= 2.6.0"
10
+
11
+ s.authors = ["John W. Long", "David Chelimsky", "Bryce Kerley"]
10
12
  s.description = %q{Radius is a powerful tag-based template language for Ruby inspired by the template languages used in MovableType and TextPattern. It uses tags similar to XML, but can be used to generate any form of plain text (HTML, e-mail, etc...).}
11
- s.email = %q{me@johnwlong.com}
13
+ s.email = ["me@johnwlong.com", "dchelimsky@gmail.com", "bkerley@brycekerley.net"]
12
14
  s.extra_rdoc_files = [
13
15
  "CHANGELOG",
14
16
  "CONTRIBUTORS",
@@ -22,5 +24,11 @@ Gem::Specification.new do |s|
22
24
 
23
25
  s.homepage = %q{http://github.com/jlong/radius}
24
26
  s.summary = %q{A tag-based templating language for Ruby.}
27
+
28
+ s.metadata = {
29
+ "homepage_uri" => "https://github.com/jlong/radius",
30
+ "changelog_uri" => "https://github.com/jlong/radius/blob/master/CHANGELOG",
31
+ "bug_tracker_uri" => "https://github.com/jlong/radius/issues"
32
+ }
25
33
  end
26
34
 
data/test/context_test.rb CHANGED
@@ -1,6 +1,6 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/test_helper')
1
+ require 'test_helper'
2
2
 
3
- class RadiusContextTest < Test::Unit::TestCase
3
+ class RadiusContextTest < Minitest::Test
4
4
  include RadiusTestHelper
5
5
 
6
6
  class SuperContext < Radius::Context
@@ -24,7 +24,7 @@ class RadiusContextTest < Test::Unit::TestCase
24
24
  assert_kind_of Radius::Context, c
25
25
  c.define_tag('test') { 'just a test' }
26
26
  end
27
- assert_not_equal Hash.new, @context.definitions
27
+ refute_equal Hash.new, @context.definitions
28
28
  end
29
29
 
30
30
  def test_initialize_with_arguments
@@ -35,6 +35,14 @@ class RadiusContextTest < Test::Unit::TestCase
35
35
  assert_equal 'arg', @context.name
36
36
  end
37
37
 
38
+ def test_dup_preserves_delegated_values
39
+ @context = Radius::Context.new
40
+ @context.globals.object = Object.new.tap { |o| def o.special_method; "special"; end }
41
+ duped = @context.dup
42
+
43
+ assert_equal "special", duped.globals.special_method, "Duped context should preserve delegated object methods"
44
+ end
45
+
38
46
  def test_with
39
47
  got = @context.with do |c|
40
48
  assert_equal @context, c
@@ -58,13 +66,13 @@ class RadiusContextTest < Test::Unit::TestCase
58
66
  def test_tag_missing
59
67
  class << @context
60
68
  def tag_missing(tag, attr, &block)
61
- "undefined tag `#{tag}' with attributes #{attr.inspect}"
69
+ "undefined tag `#{tag}' with attributes #{attr.inspect.sub(" => ", "=>")}"
62
70
  end
63
71
  end
64
72
 
65
73
  text = ''
66
74
  expected = %{undefined tag `undefined_tag' with attributes {"cool"=>"beans"}}
67
- assert_nothing_raised { text = @context.render_tag('undefined_tag', 'cool' => 'beans') }
75
+ text = @context.render_tag('undefined_tag', 'cool' => 'beans')
68
76
  assert_equal expected, text
69
77
  end
70
78
 
@@ -1,11 +1,12 @@
1
1
  require 'thread'
2
- require 'test/unit'
2
+ require 'test_helper'
3
3
  require 'radius'
4
+ require 'etc'
4
5
 
5
- class MultithreadTest < Test::Unit::TestCase
6
+ class MultithreadTest < Minitest::Test
6
7
 
7
8
  def setup
8
- Thread.abort_on_exception
9
+ Thread.abort_on_exception = true
9
10
  @context = Radius::Context.new do |c|
10
11
  c.define_tag('thread') do |tag|
11
12
  "#{tag.locals.thread_id} / #{tag.globals.object_id}"
@@ -13,51 +14,67 @@ class MultithreadTest < Test::Unit::TestCase
13
14
  end
14
15
  end
15
16
 
16
- if RUBY_PLATFORM == 'java'
17
- require 'java'
18
- # call once before the thread to keep from using hidden require in a thread
19
- Radius::Parser.new
20
- def test_runs_multithreaded
21
- lock = java.lang.String.new("lock")
22
- threads = []
23
- 1000.times do |t|
24
- thread = Thread.new do
25
- parser = Radius::Parser.new(@context, :tag_prefix => 'r')
26
- parser.context.globals.thread_id = Thread.current.object_id
27
- expected = "#{Thread.current.object_id} / "+
28
- "#{parser.context.globals.object_id}"
29
- actual = parser.parse('<r:thread />')
30
- assert_equal expected, actual
31
- end
32
- lock.synchronized do
33
- threads << thread
17
+ def test_runs_multithreaded
18
+ thread_count = [Etc.nprocessors, 16].min
19
+ iterations_per_thread = 500
20
+ failures = Queue.new
21
+ results = Array.new(thread_count) { [] }
22
+ threads = []
23
+
24
+ thread_count.times do |i|
25
+ threads << Thread.new do
26
+ local_results = []
27
+ iterations_per_thread.times do
28
+ begin
29
+ thread_context = @context.dup
30
+ parser = Radius::Parser.new(thread_context, :tag_prefix => 'r')
31
+ parser.context.globals.thread_id = Thread.current.object_id
32
+ expected = "#{Thread.current.object_id} / #{parser.context.globals.object_id}"
33
+ result = parser.parse('<r:thread />')
34
+
35
+ local_results << {
36
+ result: result,
37
+ thread_id: Thread.current.object_id,
38
+ iteration: local_results.size
39
+ }
40
+
41
+ failures << "Expected: #{expected}, Got: #{result}" unless result == expected
42
+ rescue => e
43
+ failures << "Thread #{Thread.current.object_id} failed: #{e.message}\n#{e.backtrace.join("\n")}"
44
+ end
34
45
  end
35
- end
36
- lock.synchronized do
37
- threads.each{|t| t.join }
46
+ results[i] = local_results
38
47
  end
39
48
  end
40
- else
41
- def test_runs_multithreaded
42
- threads = []
43
- mute = Mutex.new
44
- 1000.times do |t|
45
- thread = Thread.new do
46
- parser = Radius::Parser.new(@context, :tag_prefix => 'r')
47
- parser.context.globals.thread_id = Thread.current.object_id
48
- expected = "#{Thread.current.object_id} / "+
49
- "#{parser.context.globals.object_id}"
50
- actual = parser.parse('<r:thread />')
51
- assert_equal expected, actual
52
- end
53
- mute.synchronize do
54
- threads << thread
55
- end
56
- end
57
- mute.synchronize do
58
- threads.each{|t| t.join }
49
+
50
+ threads.each(&:join)
51
+
52
+ # Only try to show failures if there are any
53
+ failure_message = if failures.empty?
54
+ nil
55
+ else
56
+ failed_items = []
57
+ 5.times { failed_items << failures.pop unless failures.empty? }
58
+ "Thread failures detected:\n#{failures.size} times:\n#{failed_items.join("\n")}"
59
+ end
60
+
61
+ assert(failures.empty?, failure_message)
62
+ total_results = results.flatten.uniq.size
63
+ expected_unique_results = thread_count * iterations_per_thread
64
+
65
+ if total_results != expected_unique_results
66
+ duplicates = results.flatten.group_by { |r| r[:result] }
67
+ .select { |_, v| v.size > 1 }
68
+
69
+ puts "\nDuplicates found:"
70
+ duplicates.each do |result, occurrences|
71
+ puts "\nResult: #{result[:result]}"
72
+ occurrences.each { |o| puts " Thread: #{o[:thread_id]}, Iteration: #{o[:iteration]}" }
59
73
  end
60
74
  end
75
+
76
+ assert_equal expected_unique_results, total_results,
77
+ "Expected #{expected_unique_results} unique results (#{thread_count} threads × #{iterations_per_thread} iterations)"
61
78
  end
62
79
 
63
80
  end
@@ -1,7 +1,7 @@
1
- require 'test/unit'
1
+ require 'test_helper'
2
2
  require 'radius'
3
3
 
4
- class RadiusOrdStringTest < Test::Unit::TestCase
4
+ class RadiusOrdStringTest < Minitest::Test
5
5
 
6
6
  def test_string_slice_integer
7
7
  str = Radius::OrdString.new "abc"
@@ -15,4 +15,4 @@ class RadiusOrdStringTest < Test::Unit::TestCase
15
15
  assert_equal str[0..-1], "abc"
16
16
  end
17
17
 
18
- end
18
+ end
data/test/parser_test.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
2
 
3
- class RadiusParserTest < Test::Unit::TestCase
3
+ class RadiusParserTest < Minitest::Test
4
4
  include RadiusTestHelper
5
5
 
6
6
  def setup
@@ -39,6 +39,14 @@ class RadiusParserTest < Test::Unit::TestCase
39
39
 
40
40
  assert_parse_output 'ok', '<r:some-tag>nope</r:some-tag>'
41
41
  end
42
+
43
+ def test_parse_tag_with_dashed_attributes
44
+ define_tag 'tag-with-dashed-attributes' do |tag|
45
+ "dashed: #{tag.attr['data-dashed']} regular: #{tag.attr['regular']}"
46
+ end
47
+ assert_parse_output 'dashed: dashed-value regular: value', '<r:tag-with-dashed-attributes data-dashed="dashed-value" regular="value"></r:tag-with-dashed-attributes>'
48
+ end
49
+
42
50
 
43
51
  def test_parse_individual_tags_and_parameters
44
52
  define_tag "add" do |tag|
@@ -203,11 +211,11 @@ class RadiusParserTest < Test::Unit::TestCase
203
211
  end
204
212
  parts = %w{decima nobis augue at facer processus commodo legentis odio lectorum dolore nulla esse lius qui nonummy ullamcorper erat ii notare}
205
213
  multiplier = parts.map{|p| "#{p}=\"#{rand}\""}.join(' ')
206
- assert_nothing_raised do
214
+ assert ->{
207
215
  Timeout.timeout(10) do
208
216
  assert_parse_output " false", %{<r:set value="false" #{multiplier} /> <r:var />}
209
217
  end
210
- end
218
+ }.call
211
219
  end
212
220
 
213
221
  def test_tag_option_for
@@ -1,6 +1,6 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
2
 
3
- class QuickstartTest < Test::Unit::TestCase
3
+ class QuickstartTest < Minitest::Test
4
4
 
5
5
  def test_hello_world
6
6
  context = Radius::Context.new
@@ -83,7 +83,7 @@ class QuickstartTest < Test::Unit::TestCase
83
83
 
84
84
  class LazyContext < Radius::Context
85
85
  def tag_missing(tag, attr, &block)
86
- "<strong>ERROR: Undefined tag `#{tag}' with attributes #{attr.inspect}</strong>"
86
+ "<strong>ERROR: Undefined tag `#{tag}' with attributes #{attr.inspect.sub(" => ", "=>")}</strong>"
87
87
  end
88
88
  end
89
89
  def test_tag_missing_example
@@ -1,7 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
2
  require 'radius/parser/squiggle_scanner'
3
3
 
4
- class RadiusSquiggleTest < Test::Unit::TestCase
4
+ class RadiusSquiggleTest < Minitest::Test
5
5
  include RadiusTestHelper
6
6
 
7
7
  def setup
@@ -181,11 +181,11 @@ class RadiusSquiggleTest < Test::Unit::TestCase
181
181
  end
182
182
  parts = %w{decima nobis augue at facer processus commodo legentis odio lectorum dolore nulla esse lius qui nonummy ullamcorper erat ii notare}
183
183
  multiplier = parts.map{|p| "#{p}=\"#{rand}\""}.join(' ')
184
- assert_nothing_raised do
184
+ assert ->{
185
185
  Timeout.timeout(10) do
186
186
  assert_parse_output " false", %[{set value="false" #{multiplier} /} {var /}]
187
187
  end
188
- end
188
+ }.call
189
189
  end
190
190
 
191
191
  def test_tag_option_for
data/test/test_helper.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'timeout'
1
2
  require 'simplecov'
2
3
  SimpleCov.start do
3
4
  add_filter 'test'
@@ -8,38 +9,32 @@ if ENV['COVERALLS']
8
9
  Coveralls.wear!
9
10
  end
10
11
 
11
- require 'timeout'
12
-
13
- unless defined? RADIUS_LIB
14
-
15
- RADIUS_LIB = File.join(File.dirname(__FILE__), '..', 'lib')
16
- $LOAD_PATH << RADIUS_LIB
12
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
17
13
 
18
- require 'radius'
19
- require 'test/unit'
14
+ require 'radius'
20
15
 
21
- module RadiusTestHelper
22
- class TestContext < Radius::Context; end
16
+ module RadiusTestHelper
17
+ class TestContext < Radius::Context; end
23
18
 
24
- def new_context
25
- Radius::Context.new do |c|
26
- c.define_tag("reverse" ) { |tag| tag.expand.reverse }
27
- c.define_tag("capitalize") { |tag| tag.expand.upcase }
28
- c.define_tag("echo" ) { |tag| tag.attr['value'] }
29
- c.define_tag("wrap" ) { |tag| "[#{tag.expand}]" }
30
- c.define_tag("attr") do |tag|
31
- kv = tag.attr.keys.sort.collect{|k| "#{k.inspect}=>#{tag[k].inspect}"}
32
- "{#{kv.join(', ')}}"
33
- end
19
+ def new_context
20
+ Radius::Context.new do |c|
21
+ c.define_tag("reverse" ) { |tag| tag.expand.reverse }
22
+ c.define_tag("capitalize") { |tag| tag.expand.upcase }
23
+ c.define_tag("echo" ) { |tag| tag.attr['value'] }
24
+ c.define_tag("wrap" ) { |tag| "[#{tag.expand}]" }
25
+ c.define_tag("attr") do |tag|
26
+ kv = tag.attr.keys.sort.collect{|k| "#{k.inspect}=>#{tag[k].inspect}"}
27
+ "{#{kv.join(', ')}}"
34
28
  end
35
29
  end
30
+ end
36
31
 
37
- def define_tag(name, options = {}, &block)
38
- @parser.context.define_tag name, options, &block
39
- end
32
+ def define_tag(name, options = {}, &block)
33
+ @parser.context.define_tag name, options, &block
34
+ end
40
35
 
41
- def define_global_tag(name, options = {}, &block)
42
- @context.define_tag name, options, &block
43
- end
36
+ def define_global_tag(name, options = {}, &block)
37
+ @context.define_tag name, options, &block
44
38
  end
45
39
  end
40
+ require 'minitest/autorun'