@@ -113,6 +113,51 @@ def syntax_check(code, fname, line)
113113 end
114114 end
115115
116+ def assert_no_memory_leak ( args , prepare , code , message = nil , limit : 2.0 , rss : false , **opt )
117+ # TODO: consider choosing some appropriate limit for MJIT and stop skipping this once it does not randomly fail
118+ pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined? ( RubyVM ::MJIT ) && RubyVM ::MJIT . enabled?
119+
120+ require_relative '../../memory_status'
121+ raise MiniTest ::Skip , "unsupported platform" unless defined? ( Memory ::Status )
122+
123+ token = "\e [7;1m#{ $$. to_s } :#{ Time . now . strftime ( '%s.%L' ) } :#{ rand ( 0x10000 ) . to_s ( 16 ) } :\e [m"
124+ token_dump = token . dump
125+ token_re = Regexp . quote ( token )
126+ envs = args . shift if Array === args and Hash === args . first
127+ args = [
128+ "--disable=gems" ,
129+ "-r" , File . expand_path ( "../../../memory_status" , __FILE__ ) ,
130+ *args ,
131+ "-v" , "-" ,
132+ ]
133+ if defined? Memory ::NO_MEMORY_LEAK_ENVS then
134+ envs ||= { }
135+ newenvs = envs . merge ( Memory ::NO_MEMORY_LEAK_ENVS ) { |_ , _ , _ | break }
136+ envs = newenvs if newenvs
137+ end
138+ args . unshift ( envs ) if envs
139+ cmd = [
140+ 'END {STDERR.puts ' "#{ token_dump } " '"FINAL=#{Memory::Status.new}"}' ,
141+ prepare ,
142+ 'STDERR.puts(' "#{ token_dump } " '"START=#{$initial_status = Memory::Status.new}")' ,
143+ '$initial_size = $initial_status.size' ,
144+ code ,
145+ 'GC.start' ,
146+ ] . join ( "\n " )
147+ _ , err , status = EnvUtil . invoke_ruby ( args , cmd , true , true , **opt )
148+ before = err . sub! ( /^#{ token_re } START=(\{ .*\} )\n / , '' ) && Memory ::Status . parse ( $1)
149+ after = err . sub! ( /^#{ token_re } FINAL=(\{ .*\} )\n / , '' ) && Memory ::Status . parse ( $1)
150+ assert ( status . success? , FailDesc [ status , message , err ] )
151+ ( [ :size , ( rss && :rss ) ] & after . members ) . each do |n |
152+ b = before [ n ]
153+ a = after [ n ]
154+ next unless a > 0 and b > 0
155+ assert_operator ( a . fdiv ( b ) , :< , limit , message ( message ) { "#{ n } : #{ b } => #{ a } " } )
156+ end
157+ rescue LoadError
158+ pend
159+ end
160+
116161 # :call-seq:
117162 # assert_nothing_raised( *args, &block )
118163 #
0 commit comments