@@ -167,6 +167,7 @@ defmodule ExUnit.Callbacks do
167
167
@ ex_unit_describe nil
168
168
@ ex_unit_setup [ ]
169
169
@ ex_unit_setup_all [ ]
170
+ @ ex_unit_used_describes % { }
170
171
171
172
@ before_compile unquote ( __MODULE__ )
172
173
import unquote ( __MODULE__ )
@@ -175,7 +176,8 @@ defmodule ExUnit.Callbacks do
175
176
176
177
@ doc false
177
178
defmacro __before_compile__ ( env ) do
178
- [ compile_callbacks ( env , :setup ) , compile_callbacks ( env , :setup_all ) ]
179
+ used_describes = Module . get_attribute ( env . module , :ex_unit_used_describes )
180
+ [ compile_setup ( env , :setup , used_describes ) , compile_setup ( env , :setup_all , % { } ) ]
179
181
end
180
182
181
183
@ doc """
@@ -208,8 +210,7 @@ defmodule ExUnit.Callbacks do
208
210
do_setup ( quote ( do: _ ) , block )
209
211
else
210
212
quote do
211
- @ ex_unit_setup ExUnit.Callbacks . __callback__ ( unquote ( block ) , @ ex_unit_describe ) ++
212
- @ ex_unit_setup
213
+ ExUnit.Callbacks . __setup__ ( __MODULE__ , unquote ( block ) )
213
214
end
214
215
end
215
216
end
@@ -236,12 +237,31 @@ defmodule ExUnit.Callbacks do
236
237
237
238
defp do_setup ( context , block ) do
238
239
quote bind_quoted: [ context: escape ( context ) , block: escape ( block ) ] do
239
- name = :"__ex_unit_setup_ #{ length ( @ ex_unit_setup ) } "
240
+ name = ExUnit.Callbacks . __setup__ ( __MODULE__ )
240
241
defp unquote ( name ) ( unquote ( context ) ) , unquote ( block )
241
- @ ex_unit_setup [ { name , @ ex_unit_describe } | @ ex_unit_setup ]
242
242
end
243
243
end
244
244
245
+ @ doc false
246
+ def __setup__ ( module , callbacks ) do
247
+ setup = Module . get_attribute ( module , :ex_unit_setup )
248
+ Module . put_attribute ( module , :ex_unit_setup , Enum . reverse ( callbacks ( callbacks ) , setup ) )
249
+ end
250
+
251
+ @ doc false
252
+ def __setup__ ( module ) do
253
+ setup = Module . get_attribute ( module , :ex_unit_setup )
254
+
255
+ name =
256
+ case Module . get_attribute ( module , :ex_unit_describe ) do
257
+ { _line , _message , counter } -> :"__ex_unit_setup_#{ counter } _#{ length ( setup ) } "
258
+ nil -> :"__ex_unit_setup_#{ length ( setup ) } "
259
+ end
260
+
261
+ Module . put_attribute ( module , :ex_unit_setup , [ name | setup ] )
262
+ name
263
+ end
264
+
245
265
@ doc """
246
266
Defines a callback to be run before all tests in a case.
247
267
@@ -283,12 +303,7 @@ defmodule ExUnit.Callbacks do
283
303
do_setup_all ( quote ( do: _ ) , block )
284
304
else
285
305
quote do
286
- @ ex_unit_describe &&
287
- raise "cannot invoke setup_all/1 inside describe as setup_all/1 " <>
288
- "always applies to all tests in a module"
289
-
290
- @ ex_unit_setup_all ExUnit.Callbacks . __callback__ ( unquote ( block ) , nil ) ++
291
- @ ex_unit_setup_all
306
+ ExUnit.Callbacks . __setup_all__ ( __MODULE__ , unquote ( block ) )
292
307
end
293
308
end
294
309
end
@@ -312,10 +327,48 @@ defmodule ExUnit.Callbacks do
312
327
313
328
defp do_setup_all ( context , block ) do
314
329
quote bind_quoted: [ context: escape ( context ) , block: escape ( block ) ] do
315
- @ ex_unit_describe && raise "cannot invoke setup_all/2 inside describe"
316
- name = :"__ex_unit_setup_all_#{ length ( @ ex_unit_setup_all ) } "
330
+ name = ExUnit.Callbacks . __setup_all__ ( __MODULE__ )
317
331
defp unquote ( name ) ( unquote ( context ) ) , unquote ( block )
318
- @ ex_unit_setup_all [ { name , nil } | @ ex_unit_setup_all ]
332
+ end
333
+ end
334
+
335
+ @ doc false
336
+ def __setup_all__ ( module , callbacks ) do
337
+ no_describe! ( module )
338
+ setup_all = Module . get_attribute ( module , :ex_unit_setup_all )
339
+
340
+ Module . put_attribute (
341
+ module ,
342
+ :ex_unit_setup_all ,
343
+ Enum . reverse ( callbacks ( callbacks ) , setup_all )
344
+ )
345
+ end
346
+
347
+ @ doc false
348
+ def __setup_all__ ( module ) do
349
+ no_describe! ( module )
350
+ setup_all = Module . get_attribute ( module , :ex_unit_setup_all )
351
+ name = :"__ex_unit_setup_all_#{ length ( setup_all ) } "
352
+ Module . put_attribute ( module , :ex_unit_setup_all , [ name | setup_all ] )
353
+ name
354
+ end
355
+
356
+ defp no_describe! ( module ) do
357
+ if Module . get_attribute ( module , :ex_unit_describe ) do
358
+ raise "cannot invoke setup_all/1-2 inside describe as setup_all " <>
359
+ "always applies to all tests in a module"
360
+ end
361
+ end
362
+
363
+ defp callbacks ( callbacks ) do
364
+ for k <- List . wrap ( callbacks ) do
365
+ if not is_atom ( k ) do
366
+ raise ArgumentError ,
367
+ "setup/setup_all expect a callback name as an atom or " <>
368
+ "a list of callback names, got: #{ inspect ( k ) } "
369
+ end
370
+
371
+ k
319
372
end
320
373
end
321
374
@@ -558,8 +611,8 @@ defmodule ExUnit.Callbacks do
558
611
raise_merge_failed! ( mod , original_value )
559
612
end
560
613
561
- defp merge ( mod , context , data , original_value ) when is_list ( data ) do
562
- merge ( mod , context , Map . new ( data ) , original_value )
614
+ defp merge ( mod , context , data , _original_value ) when is_list ( data ) do
615
+ context_merge ( mod , context , Map . new ( data ) )
563
616
end
564
617
565
618
defp merge ( mod , context , data , _original_value ) when is_map ( data ) do
@@ -594,44 +647,112 @@ defmodule ExUnit.Callbacks do
594
647
Macro . escape ( contents , unquote: true )
595
648
end
596
649
597
- defp compile_callbacks ( env , kind ) do
598
- callbacks = Module . get_attribute ( env . module , :"ex_unit_#{ kind } " ) |> Enum . reverse ( )
650
+ @ doc false
651
+ def __describe__ ( module , line , message , fun ) do
652
+ if Module . get_attribute ( module , :ex_unit_describe ) do
653
+ raise "cannot call \" describe\" inside another \" describe\" . See the documentation " <>
654
+ "for ExUnit.Case.describe/2 on named setups and how to handle hierarchies"
655
+ end
656
+
657
+ used_describes = Module . get_attribute ( module , :ex_unit_used_describes )
658
+
659
+ cond do
660
+ not is_binary ( message ) ->
661
+ raise ArgumentError , "describe name must be a string, got: #{ inspect ( message ) } "
599
662
600
- acc =
601
- case callbacks do
602
- [ ] ->
603
- quote ( do: context )
663
+ is_map_key ( used_describes , message ) ->
664
+ raise ExUnit.DuplicateDescribeError ,
665
+ "describe #{ inspect ( message ) } is already defined in #{ inspect ( module ) } "
604
666
605
- [ h | t ] ->
606
- Enum . reduce ( t , compile_merge ( h ) , fn callback_describe , acc ->
667
+ true ->
668
+ :ok
669
+ end
670
+
671
+ if Module . get_attribute ( module , :describetag ) != [ ] do
672
+ raise "@describetag must be set inside describe/2 blocks"
673
+ end
674
+
675
+ setup = Module . get_attribute ( module , :ex_unit_setup )
676
+ Module . put_attribute ( module , :ex_unit_setup , [ ] )
677
+ Module . put_attribute ( module , :ex_unit_describe , { line , message , map_size ( used_describes ) } )
678
+
679
+ try do
680
+ fun . ( message , used_describes )
681
+ after
682
+ Module . put_attribute ( module , :ex_unit_describe , nil )
683
+ Module . put_attribute ( module , :ex_unit_setup , setup )
684
+ Module . delete_attribute ( module , :describetag )
685
+
686
+ for attribute <- Module . get_attribute ( module , :ex_unit_registered_describe_attributes ) do
687
+ Module . delete_attribute ( module , attribute )
688
+ end
689
+ end
690
+ end
691
+
692
+ @ doc false
693
+ def __describe__ ( module , message , used_describes ) do
694
+ { name , body } =
695
+ case Module . get_attribute ( module , :ex_unit_setup ) do
696
+ [ ] -> { nil , nil }
697
+ callbacks -> { :"__ex_unit_describe_#{ map_size ( used_describes ) } " , compile_setup ( callbacks ) }
698
+ end
699
+
700
+ used_describes = Map . put ( used_describes , message , name )
701
+ Module . put_attribute ( module , :ex_unit_used_describes , used_describes )
702
+ { name , body }
703
+ end
704
+
705
+ defp compile_setup ( env , kind , describes ) do
706
+ calls =
707
+ env . module
708
+ |> Module . get_attribute ( :"ex_unit_#{ kind } " )
709
+ |> compile_setup ( )
710
+
711
+ describe_clauses =
712
+ for { describe , callback } <- describes ,
713
+ callback != nil ,
714
+ clause <- quote ( do: ( unquote ( describe ) -> unquote ( callback ) ( var! ( context ) ) ) ) ,
715
+ do: clause
716
+
717
+ body =
718
+ if describe_clauses == [ ] do
719
+ calls
720
+ else
721
+ describe_clauses =
722
+ describe_clauses ++
607
723
quote do
608
- context = unquote ( acc )
609
- unquote ( compile_merge ( callback_describe ) )
724
+ _ -> var! ( context )
610
725
end
611
- end )
726
+
727
+ quote do
728
+ var! ( context ) = unquote ( calls )
729
+ case Map . get ( var! ( context ) , :describe , nil ) , do: unquote ( describe_clauses )
730
+ end
612
731
end
613
732
614
733
quote do
615
- def __ex_unit__ ( unquote ( kind ) , context ) do
616
- describe = Map . get ( context , :describe , nil )
617
- unquote ( acc )
618
- end
734
+ def __ex_unit__ ( unquote ( kind ) , var! ( context ) ) , do: unquote ( body )
619
735
end
620
736
end
621
737
622
- defp compile_merge ( { callback , nil } ) do
623
- quote do
624
- unquote ( __MODULE__ ) . __merge__ ( __MODULE__ , context , unquote ( callback ) ( context ) )
625
- end
738
+ defp compile_setup ( [ ] ) do
739
+ quote ( do: var! ( context ) )
626
740
end
627
741
628
- defp compile_merge ( { callback , { _line , describe } } ) do
629
- quote do
630
- if unquote ( describe ) == describe do
631
- unquote ( compile_merge ( { callback , nil } ) )
632
- else
633
- context
742
+ defp compile_setup ( callbacks ) do
743
+ [ h | t ] = Enum . reverse ( callbacks )
744
+
745
+ Enum . reduce ( t , compile_setup_call ( h ) , fn callback , acc ->
746
+ quote do
747
+ var! ( context ) = unquote ( acc )
748
+ unquote ( compile_setup_call ( callback ) )
634
749
end
750
+ end )
751
+ end
752
+
753
+ defp compile_setup_call ( callback ) do
754
+ quote do
755
+ unquote ( __MODULE__ ) . __merge__ ( __MODULE__ , var! ( context ) , unquote ( callback ) ( var! ( context ) ) )
635
756
end
636
757
end
637
758
end
0 commit comments