File Coverage

blib/lib/Testcontainers/Labels.pm
Criterion Covered Total %
statement 57 57 100.0
branch 6 6 100.0
condition 2 5 40.0
subroutine 15 15 100.0
pod 0 3 0.0
total 80 86 93.0


line stmt bran cond sub pod time code
1             package Testcontainers::Labels;
2             # ABSTRACT: Standard Testcontainers labels for Docker resources
3              
4 4     4   39 use strict;
  4         8  
  4         130  
5 4     4   86 use warnings;
  4         8  
  4         173  
6 4     4   33 use Carp qw( croak );
  4         8  
  4         199  
7 4     4   19 use Exporter 'import';
  4         54  
  4         535  
8              
9             our $VERSION = '0.001';
10              
11             our @EXPORT_OK = qw(
12             LABEL_BASE
13             LABEL_LANG
14             LABEL_VERSION
15             LABEL_SESSION_ID
16             LABEL_REAPER
17             LABEL_RYUK
18             LABEL_REAP
19             default_labels
20             merge_custom_labels
21             session_id
22             );
23              
24             our %EXPORT_TAGS = (
25             constants => [qw(
26             LABEL_BASE
27             LABEL_LANG
28             LABEL_VERSION
29             LABEL_SESSION_ID
30             LABEL_REAPER
31             LABEL_RYUK
32             LABEL_REAP
33             )],
34             all => \@EXPORT_OK,
35             );
36              
37             =head1 SYNOPSIS
38              
39             use Testcontainers::Labels qw( default_labels merge_custom_labels session_id );
40              
41             # Generate a session ID for this test run
42             my $sid = session_id();
43              
44             # Get the standard labels every container should carry
45             my %labels = default_labels($sid);
46              
47             # Safely merge user labels (rejects org.testcontainers.* overrides)
48             my %merged = merge_custom_labels(\%labels, { app => 'mytest' });
49              
50             =head1 DESCRIPTION
51              
52             Implements the Testcontainers label specification as defined by the reference
53             implementations (Go, Java). Every container created by Testcontainers carries
54             a set of well-known labels that identify it as managed by the framework, enable
55             Ryuk cleanup, and record session metadata.
56              
57             =head1 LABEL CONSTANTS
58              
59             The following constants mirror the Go reference at
60             C:
61              
62             =over
63              
64             =item C - C
65              
66             Marker label. Always set to C<"true">.
67              
68             =item C - C
69              
70             Language of the client library. Set to C<"perl">.
71              
72             =item C - C
73              
74             Version of the Testcontainers library.
75              
76             =item C - C
77              
78             A unique identifier tying resources to a single test session. Used by Ryuk
79             to determine which resources to reap.
80              
81             =item C - C
82              
83             Labels the Ryuk container itself.
84              
85             =item C - C
86              
87             Labels the Ryuk container itself (alternate key).
88              
89             =item C - C
90              
91             When set to C<"true">, signals that the resource should be reaped by Ryuk.
92              
93             =back
94              
95             =cut
96              
97 4     4   24 use constant LABEL_BASE => 'org.testcontainers';
  4         7  
  4         459  
98 4     4   103 use constant LABEL_LANG => 'org.testcontainers.lang';
  4         8  
  4         210  
99 4     4   24 use constant LABEL_VERSION => 'org.testcontainers.version';
  4         5  
  4         211  
100 4     4   27 use constant LABEL_SESSION_ID => 'org.testcontainers.sessionId';
  4         6  
  4         259  
101 4     4   19 use constant LABEL_REAPER => 'org.testcontainers.reaper';
  4         5  
  4         215  
102 4     4   28 use constant LABEL_RYUK => 'org.testcontainers.ryuk';
  4         14  
  4         206  
103 4     4   16 use constant LABEL_REAP => 'org.testcontainers.reap';
  4         6  
  4         1800  
104              
105             # ---------------------------------------------------------------------------
106             # Session ID — generated once per process and reused.
107             # ---------------------------------------------------------------------------
108              
109             my $_session_id;
110              
111             sub session_id {
112 7 100   7 0 3855 return $_session_id if defined $_session_id;
113 1         4 $_session_id = _generate_uuid_v4();
114 1         4 return $_session_id;
115             }
116              
117             =func session_id()
118              
119             Returns a UUID v4 string that uniquely identifies the current test session.
120             Generated lazily on first call and cached for the lifetime of the process.
121              
122             =cut
123              
124             # ---------------------------------------------------------------------------
125             # default_labels($session_id)
126             # ---------------------------------------------------------------------------
127              
128             sub default_labels {
129 5     5 0 8 my ($sid) = @_;
130 5   33     7 $sid //= session_id();
131              
132 5         17 my %labels = (
133             LABEL_BASE ,=> 'true',
134             LABEL_LANG ,=> 'perl',
135             LABEL_VERSION ,=> $Testcontainers::Labels::VERSION,
136             LABEL_SESSION_ID ,=> $sid,
137             );
138              
139             # Add reap label unless Ryuk is disabled
140 5 100       11 unless ($ENV{TESTCONTAINERS_RYUK_DISABLED}) {
141 4         5 $labels{ LABEL_REAP() } = 'true';
142             }
143              
144 5         21 return %labels;
145             }
146              
147             =func default_labels($session_id?)
148              
149             Returns a hash of the standard labels that every Testcontainers-managed
150             container should carry. If C<$session_id> is omitted the per-process
151             session ID is used automatically.
152              
153             When the environment variable C is set to a
154             true value, the C label is omitted.
155              
156             =cut
157              
158             # ---------------------------------------------------------------------------
159             # merge_custom_labels(\%defaults, \%custom)
160             # ---------------------------------------------------------------------------
161              
162             sub merge_custom_labels {
163 5     5 0 9 my ($defaults, $custom) = @_;
164 5   50     7 $custom //= {};
165              
166 5         26 my %merged = %{$defaults};
  5         15  
167              
168 5         6 for my $key (keys %{$custom}) {
  5         13  
169 2 100       7 if ($key =~ /^org\.testcontainers(?:\.|$)/) {
170 1         15 croak "Custom label '$key' uses the reserved 'org.testcontainers' "
171             . "prefix; built-in labels cannot be overridden";
172             }
173 1         3 $merged{$key} = $custom->{$key};
174             }
175              
176 4         13 return %merged;
177             }
178              
179             =func merge_custom_labels(\%defaults, \%custom)
180              
181             Merges user-supplied labels into the defaults. Croaks if any custom label
182             key starts with C to prevent accidental overrides of the
183             well-known labels.
184              
185             =cut
186              
187             # ---------------------------------------------------------------------------
188             # Internal: lightweight UUID v4 generator (no external deps)
189             # ---------------------------------------------------------------------------
190              
191             sub _generate_uuid_v4 {
192             # 16 random bytes
193 1     1   3 my @bytes = map { int(rand(256)) } 1 .. 16;
  16         64  
194              
195             # Set version 4 (bits 48-51)
196 1         4 $bytes[6] = ($bytes[6] & 0x0f) | 0x40;
197              
198             # Set variant 10xx (bits 64-65)
199 1         2 $bytes[8] = ($bytes[8] & 0x3f) | 0x80;
200              
201 1         6 return sprintf(
202             '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x',
203             @bytes,
204             );
205             }
206              
207             1;