ft-ps-tester 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Italo Almeida
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: ft_ps_tester
3
+ Version: 1.0.0
4
+ Summary: A Python tester for the 42 push_swap project with controlled disorder generation and performance grading.
5
+ Author: Italo Almeida
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/italoalmeida0/ft_ps_tester
8
+ Project-URL: Issues, https://github.com/italoalmeida0/ft_ps_tester/issues
9
+ Keywords: 42,push_swap,tester,sorting,algorithm
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.7
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Education :: Testing
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Requires-Python: >=3.7
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Dynamic: license-file
26
+
27
+ # ft_ps_tester
28
+
29
+ A Python-based tester for the **42 push_swap** project. It generates controlled random sequences with specific disorder levels, runs your `push_swap` executable, validates the output, and grades performance against 42 thresholds.
30
+
31
+ ---
32
+
33
+ ## Features
34
+
35
+ - **Controlled disorder generation** — creates sequences with precise inversion percentages.
36
+ - **Four test modes** — simple, medium, complex, and adaptive.
37
+ - **Output validation** — simulates operations to verify sorting correctness.
38
+ - **Performance grading** — compares operation counts against 42 thresholds (excellent / good / pass / fail).
39
+ - **Failure report** — concise summary of timeouts, invalid operations, and limit exceedances.
40
+
41
+ ---
42
+
43
+ ## Requirements
44
+
45
+ - Python 3
46
+ - A compiled `push_swap` executable that accepts arguments and prints operations to `stdout`
47
+
48
+ ---
49
+
50
+ ## Installation
51
+
52
+ ### Option 1: Install from PyPI (recommended)
53
+
54
+ ```bash
55
+ pip install ft_ps_tester
56
+ ```
57
+
58
+ Then run from anywhere inside your **push_swap** project:
59
+
60
+ ```bash
61
+ ft_ps_tester ./push_swap
62
+ ```
63
+
64
+ ### Option 2: Install from source
65
+
66
+ Clone this repository:
67
+
68
+ ```bash
69
+ git clone https://github.com/italoalmeida0/ft_ps_tester.git
70
+ cd ft_ps_tester
71
+ ```
72
+
73
+ Install in editable / development mode:
74
+
75
+ ```bash
76
+ pip install -e .
77
+ ```
78
+
79
+ Or install normally:
80
+
81
+ ```bash
82
+ pip install .
83
+ ```
84
+
85
+ Make sure your `push_swap` binary is compiled and executable:
86
+
87
+ ```bash
88
+ make
89
+ chmod +x push_swap
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Usage
95
+
96
+ ### Full test suite (recommended)
97
+
98
+ Tests **100** and **500** elements across all four modes (100 tests each):
99
+
100
+ ```bash
101
+ python3 ft_ps_tester.py ./push_swap
102
+ ```
103
+
104
+ ### Single test run
105
+
106
+ Test a specific size and mode:
107
+
108
+ ```bash
109
+ python3 ft_ps_tester.py ./push_swap <size> <mode>
110
+ ```
111
+
112
+ Example:
113
+
114
+ ```bash
115
+ python3 ft_ps_tester.py ./push_swap 500 complex
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Modes / Flags
121
+
122
+ Your `push_swap` must support the following flags (passed as `--<mode>` before the numbers):
123
+
124
+ | Mode | Disorder range | Description |
125
+ |-----------|----------------|------------------------------------------|
126
+ | `simple` | 15.0% – 19.9% | Nearly sorted sequences |
127
+ | `medium` | 20.0% – 49.9% | Moderately shuffled sequences |
128
+ | `complex` | 50.0% – 55.0% | Heavily shuffled sequences |
129
+ | `adaptive`| 15.0% – 55.0% | Random disorder across the full spectrum |
130
+
131
+ > **Note:** If your `push_swap` does **not** implement these flags, the tester will still work if your program ignores unknown flags and simply sorts the provided numbers. However, for accurate mode-based testing, your `push_swap` should parse and use the flag to adjust its algorithm.
132
+
133
+ ---
134
+
135
+ ## Grading Thresholds
136
+
137
+ | Size | Excellent | Good | Pass |
138
+ |------|-----------|-------|-------|
139
+ | 100 | < 700 | < 1500| ≤ 2000|
140
+ | 500 | < 5500 | < 8000| ≤ 12000|
141
+
142
+ Results are shown with color-coded grades:
143
+ - **EXCELLENT** — green
144
+ - **GOOD** — blue
145
+ - **PASS** — yellow
146
+ - **FAIL** — red
147
+
148
+ ---
149
+
150
+ ## Example Output
151
+
152
+ ```
153
+ Running FULL TEST SUITE for ./push_swap
154
+
155
+ >> Testing Size: 100 | Mode: SIMPLE ..................................................
156
+ >> Testing Size: 100 | Mode: MEDIUM ..................................................
157
+ >> Testing Size: 100 | Mode: COMPLEX ..................................................
158
+ >> Testing Size: 100 | Mode: ADAPTIVE..................................................
159
+ >> Testing Size: 500 | Mode: SIMPLE ..................................................
160
+ >> Testing Size: 500 | Mode: MEDIUM ..................................................
161
+ >> Testing Size: 500 | Mode: COMPLEX ..................................................
162
+ >> Testing Size: 500 | Mode: ADAPTIVE..................................................
163
+
164
+ ========================================================================================
165
+ PERFORMANCE SUMMARY
166
+ ========================================================================================
167
+ SIZE | MODE | MAX (GRADE) | MIN (GRADE) | AVG (GRADE) | FAILS
168
+ ----------------------------------------------------------------------------------------
169
+ 100 | SIMPLE | 450 (EXCELLENT) | 320 (EXCELLENT) | 380 (EXCELLENT) | 0
170
+ 100 | MEDIUM | 1200 (GOOD) | 900 (EXCELLENT) | 1050 (GOOD) | 0
171
+ ...
172
+ ```
173
+
174
+ ---
175
+
176
+ ## License
177
+
178
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,152 @@
1
+ # ft_ps_tester
2
+
3
+ A Python-based tester for the **42 push_swap** project. It generates controlled random sequences with specific disorder levels, runs your `push_swap` executable, validates the output, and grades performance against 42 thresholds.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - **Controlled disorder generation** — creates sequences with precise inversion percentages.
10
+ - **Four test modes** — simple, medium, complex, and adaptive.
11
+ - **Output validation** — simulates operations to verify sorting correctness.
12
+ - **Performance grading** — compares operation counts against 42 thresholds (excellent / good / pass / fail).
13
+ - **Failure report** — concise summary of timeouts, invalid operations, and limit exceedances.
14
+
15
+ ---
16
+
17
+ ## Requirements
18
+
19
+ - Python 3
20
+ - A compiled `push_swap` executable that accepts arguments and prints operations to `stdout`
21
+
22
+ ---
23
+
24
+ ## Installation
25
+
26
+ ### Option 1: Install from PyPI (recommended)
27
+
28
+ ```bash
29
+ pip install ft_ps_tester
30
+ ```
31
+
32
+ Then run from anywhere inside your **push_swap** project:
33
+
34
+ ```bash
35
+ ft_ps_tester ./push_swap
36
+ ```
37
+
38
+ ### Option 2: Install from source
39
+
40
+ Clone this repository:
41
+
42
+ ```bash
43
+ git clone https://github.com/italoalmeida0/ft_ps_tester.git
44
+ cd ft_ps_tester
45
+ ```
46
+
47
+ Install in editable / development mode:
48
+
49
+ ```bash
50
+ pip install -e .
51
+ ```
52
+
53
+ Or install normally:
54
+
55
+ ```bash
56
+ pip install .
57
+ ```
58
+
59
+ Make sure your `push_swap` binary is compiled and executable:
60
+
61
+ ```bash
62
+ make
63
+ chmod +x push_swap
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Usage
69
+
70
+ ### Full test suite (recommended)
71
+
72
+ Tests **100** and **500** elements across all four modes (100 tests each):
73
+
74
+ ```bash
75
+ python3 ft_ps_tester.py ./push_swap
76
+ ```
77
+
78
+ ### Single test run
79
+
80
+ Test a specific size and mode:
81
+
82
+ ```bash
83
+ python3 ft_ps_tester.py ./push_swap <size> <mode>
84
+ ```
85
+
86
+ Example:
87
+
88
+ ```bash
89
+ python3 ft_ps_tester.py ./push_swap 500 complex
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Modes / Flags
95
+
96
+ Your `push_swap` must support the following flags (passed as `--<mode>` before the numbers):
97
+
98
+ | Mode | Disorder range | Description |
99
+ |-----------|----------------|------------------------------------------|
100
+ | `simple` | 15.0% – 19.9% | Nearly sorted sequences |
101
+ | `medium` | 20.0% – 49.9% | Moderately shuffled sequences |
102
+ | `complex` | 50.0% – 55.0% | Heavily shuffled sequences |
103
+ | `adaptive`| 15.0% – 55.0% | Random disorder across the full spectrum |
104
+
105
+ > **Note:** If your `push_swap` does **not** implement these flags, the tester will still work if your program ignores unknown flags and simply sorts the provided numbers. However, for accurate mode-based testing, your `push_swap` should parse and use the flag to adjust its algorithm.
106
+
107
+ ---
108
+
109
+ ## Grading Thresholds
110
+
111
+ | Size | Excellent | Good | Pass |
112
+ |------|-----------|-------|-------|
113
+ | 100 | < 700 | < 1500| ≤ 2000|
114
+ | 500 | < 5500 | < 8000| ≤ 12000|
115
+
116
+ Results are shown with color-coded grades:
117
+ - **EXCELLENT** — green
118
+ - **GOOD** — blue
119
+ - **PASS** — yellow
120
+ - **FAIL** — red
121
+
122
+ ---
123
+
124
+ ## Example Output
125
+
126
+ ```
127
+ Running FULL TEST SUITE for ./push_swap
128
+
129
+ >> Testing Size: 100 | Mode: SIMPLE ..................................................
130
+ >> Testing Size: 100 | Mode: MEDIUM ..................................................
131
+ >> Testing Size: 100 | Mode: COMPLEX ..................................................
132
+ >> Testing Size: 100 | Mode: ADAPTIVE..................................................
133
+ >> Testing Size: 500 | Mode: SIMPLE ..................................................
134
+ >> Testing Size: 500 | Mode: MEDIUM ..................................................
135
+ >> Testing Size: 500 | Mode: COMPLEX ..................................................
136
+ >> Testing Size: 500 | Mode: ADAPTIVE..................................................
137
+
138
+ ========================================================================================
139
+ PERFORMANCE SUMMARY
140
+ ========================================================================================
141
+ SIZE | MODE | MAX (GRADE) | MIN (GRADE) | AVG (GRADE) | FAILS
142
+ ----------------------------------------------------------------------------------------
143
+ 100 | SIMPLE | 450 (EXCELLENT) | 320 (EXCELLENT) | 380 (EXCELLENT) | 0
144
+ 100 | MEDIUM | 1200 (GOOD) | 900 (EXCELLENT) | 1050 (GOOD) | 0
145
+ ...
146
+ ```
147
+
148
+ ---
149
+
150
+ ## License
151
+
152
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,3 @@
1
+ """ft_ps_tester - A tester for the 42 push_swap project."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import sys
4
+ import os
5
+ import random
6
+ import subprocess
7
+ from collections import deque
8
+
9
+ # ==========================================
10
+ # CONFIGURATION
11
+ # ==========================================
12
+ COLORS = {
13
+ "GREEN": "\033[1;32m",
14
+ "RED": "\033[1;31m",
15
+ "YELLOW": "\033[1;33m",
16
+ "BLUE": "\033[1;34m",
17
+ "CYAN": "\033[1;36m",
18
+ "MAGENTA": "\033[1;35m",
19
+ "RESET": "\033[0m",
20
+ "BOLD": "\033[1m"
21
+ }
22
+
23
+ THRESHOLDS = {
24
+ 100: {"excellent": 700, "good": 1500, "pass": 2000},
25
+ 500: {"excellent": 5500, "good": 8000, "pass": 12000}
26
+ }
27
+
28
+ MODES = {
29
+ "simple": (15.0, 19.9),
30
+ "medium": (20.0, 49.9),
31
+ "complex": (50.0, 55.0),
32
+ "adaptive": (15.0, 55.0)
33
+ }
34
+
35
+ # ==========================================
36
+ # DATA GENERATOR
37
+ # ==========================================
38
+ def generate_sequence(size, target_disorder):
39
+ raw_sequence = random.sample(range(-1000000, 1000000), size)
40
+ raw_sequence.sort()
41
+
42
+ total_pairs = (size * (size - 1)) / 2.0
43
+ target_inv = int((target_disorder / 100.0) * total_pairs)
44
+
45
+ if target_inv > 0:
46
+ inv = [0] * size
47
+ indices = list(range(size))
48
+ random.shuffle(indices)
49
+
50
+ remaining = target_inv
51
+ for i in indices:
52
+ max_cap = size - 1 - i
53
+ take = random.randint(0, min(remaining, max_cap))
54
+ inv[i] = take
55
+ remaining -= take
56
+
57
+ if remaining > 0:
58
+ random.shuffle(indices)
59
+ for i in indices:
60
+ max_cap = size - 1 - i
61
+ space = max_cap - inv[i]
62
+ if space > 0:
63
+ take = min(remaining, space)
64
+ inv[i] += take
65
+ remaining -= take
66
+ if remaining == 0:
67
+ break
68
+
69
+ result_sequence = []
70
+ for i in range(size - 1, -1, -1):
71
+ val = raw_sequence[i]
72
+ insert_pos = inv[i]
73
+ result_sequence.insert(insert_pos, val)
74
+
75
+ return result_sequence
76
+
77
+ return raw_sequence
78
+
79
+ # ==========================================
80
+ # VALIDATOR (CHECKER)
81
+ # ==========================================
82
+ class PushSwapChecker:
83
+ def __init__(self, sequence):
84
+ self.stack_a = deque(sequence)
85
+ self.stack_b = deque()
86
+
87
+ def exec_op(self, op):
88
+ if op == "sa" and len(self.stack_a) >= 2:
89
+ self.stack_a[0], self.stack_a[1] = self.stack_a[1], self.stack_a[0]
90
+ elif op == "sb" and len(self.stack_b) >= 2:
91
+ self.stack_b[0], self.stack_b[1] = self.stack_b[1], self.stack_b[0]
92
+ elif op == "ss":
93
+ self.exec_op("sa")
94
+ self.exec_op("sb")
95
+ elif op == "pa" and len(self.stack_b) >= 1:
96
+ self.stack_a.appendleft(self.stack_b.popleft())
97
+ elif op == "pb" and len(self.stack_a) >= 1:
98
+ self.stack_b.appendleft(self.stack_a.popleft())
99
+ elif op == "ra" and len(self.stack_a) >= 2:
100
+ self.stack_a.append(self.stack_a.popleft())
101
+ elif op == "rb" and len(self.stack_b) >= 2:
102
+ self.stack_b.append(self.stack_b.popleft())
103
+ elif op == "rr":
104
+ self.exec_op("ra")
105
+ self.exec_op("rb")
106
+ elif op == "rra" and len(self.stack_a) >= 2:
107
+ self.stack_a.appendleft(self.stack_a.pop())
108
+ elif op == "rrb" and len(self.stack_b) >= 2:
109
+ self.stack_b.appendleft(self.stack_b.pop())
110
+ elif op == "rrr":
111
+ self.exec_op("rra")
112
+ self.exec_op("rrb")
113
+ else:
114
+ return False
115
+ return True
116
+
117
+ def validate(self, ops_list):
118
+ for op in ops_list:
119
+ if not self.exec_op(op):
120
+ return False
121
+
122
+ if len(self.stack_b) != 0:
123
+ return False
124
+
125
+ lst = list(self.stack_a)
126
+ return all(lst[i] <= lst[i+1] for i in range(len(lst)-1))
127
+
128
+ # ==========================================
129
+ # TEST ENGINE
130
+ # ==========================================
131
+ def get_grade_info(size, ops):
132
+ if size not in THRESHOLDS:
133
+ return ("UNKNOWN", COLORS["CYAN"])
134
+
135
+ t = THRESHOLDS[size]
136
+ if ops == 0:
137
+ return ("N/A", COLORS["RESET"])
138
+ elif ops < t["excellent"]:
139
+ return ("EXCELLENT", COLORS["GREEN"])
140
+ elif ops < t["good"]:
141
+ return ("GOOD", COLORS["BLUE"])
142
+ elif ops <= t["pass"]:
143
+ return ("PASS", COLORS["YELLOW"])
144
+ else:
145
+ return ("FAIL", COLORS["RED"])
146
+
147
+ def format_grade_column(ops, grade_text, color):
148
+ visible_str = f"{ops} ({grade_text})"
149
+ padding = 18 - len(visible_str)
150
+ return f"{ops} ({color}{grade_text}{COLORS['RESET']}){' ' * padding}"
151
+
152
+ def run_test_suite(executable, size, mode):
153
+ min_disorder, max_disorder = MODES[mode]
154
+
155
+ print(f"{COLORS['CYAN']}>> Testing Size: {size} | Mode: {mode.upper()} {COLORS['RESET']}", end=" ")
156
+
157
+ total_ops = 0
158
+ max_ops = 0
159
+ min_ops = float('inf')
160
+
161
+ failures = []
162
+
163
+ for i in range(1, 101):
164
+ disorder = random.uniform(min_disorder, max_disorder)
165
+ sequence = generate_sequence(size, disorder)
166
+ str_seq = [str(x) for x in sequence]
167
+ try:
168
+ result = subprocess.run(
169
+ [executable, f'--{mode}'] + str_seq,
170
+ capture_output=True,
171
+ text=True,
172
+ check=False,
173
+ timeout=10
174
+ )
175
+
176
+ ops = result.stdout.strip().split('\n')
177
+ if ops == ['']:
178
+ ops = []
179
+
180
+ op_count = len(ops)
181
+ checker = PushSwapChecker(sequence)
182
+ is_sorted = checker.validate(ops)
183
+
184
+ if not is_sorted:
185
+ sys.stdout.write(f"{COLORS['RED']}!{COLORS['RESET']}")
186
+ failures.append({
187
+ "size": size,
188
+ "mode": mode,
189
+ "disorder": disorder,
190
+ "reason": "Failed to sort stack properly (or invalid operation).",
191
+ "ops": op_count,
192
+ "limit": THRESHOLDS[size]["pass"],
193
+ "sequence": " ".join(str_seq)
194
+ })
195
+ else:
196
+ total_ops += op_count
197
+ max_ops = max(max_ops, op_count)
198
+ min_ops = min(min_ops, op_count)
199
+
200
+ if op_count > THRESHOLDS[size]["pass"]:
201
+ sys.stdout.write(f"{COLORS['RED']}F{COLORS['RESET']}")
202
+ failures.append({
203
+ "size": size,
204
+ "mode": mode,
205
+ "disorder": disorder,
206
+ "reason": "Operation limit exceeded.",
207
+ "ops": op_count,
208
+ "limit": THRESHOLDS[size]["pass"],
209
+ "sequence": " ".join(str_seq)
210
+ })
211
+ else:
212
+ sys.stdout.write(f"{COLORS['GREEN']}.{COLORS['RESET']}")
213
+
214
+ except subprocess.TimeoutExpired:
215
+ sys.stdout.write(f"{COLORS['MAGENTA']}T{COLORS['RESET']}")
216
+ failures.append({
217
+ "size": size,
218
+ "mode": mode,
219
+ "disorder": disorder,
220
+ "reason": "Timeout (infinite loop?).",
221
+ "ops": "N/A",
222
+ "limit": THRESHOLDS[size]["pass"],
223
+ "sequence": " ".join(str_seq)
224
+ })
225
+
226
+ sys.stdout.flush()
227
+
228
+ print()
229
+
230
+ successful_runs = 100 - len(failures)
231
+ avg_ops = (total_ops // successful_runs) if successful_runs > 0 else 0
232
+
233
+ return {
234
+ "size": size,
235
+ "mode": mode,
236
+ "max": max_ops,
237
+ "min": min_ops if min_ops != float('inf') else 0,
238
+ "avg": avg_ops,
239
+ "fails": len(failures)
240
+ }, failures
241
+
242
+ def print_failures(failures):
243
+ if not failures:
244
+ return
245
+
246
+ filtered_failures = []
247
+ seen_limits = {}
248
+
249
+ # Filter failures: max 1 timeout and 1 standard error per (size, mode)
250
+ for f in failures:
251
+ key = (f['size'], f['mode'])
252
+ if key not in seen_limits:
253
+ seen_limits[key] = {"timeout": False, "standard": False}
254
+
255
+ is_timeout = "Timeout" in f['reason']
256
+
257
+ if is_timeout and not seen_limits[key]["timeout"]:
258
+ filtered_failures.append(f)
259
+ seen_limits[key]["timeout"] = True
260
+ elif not is_timeout and not seen_limits[key]["standard"]:
261
+ filtered_failures.append(f)
262
+ seen_limits[key]["standard"] = True
263
+
264
+ print("\n" + "="*80)
265
+ print(f"{COLORS['RED']}{COLORS['BOLD']}FAILURE REPORT {COLORS['RESET']}")
266
+ print("="*80)
267
+
268
+ for idx, f in enumerate(filtered_failures):
269
+ print(f"\n{COLORS['YELLOW']}--- Failure {idx + 1} ---{COLORS['RESET']}")
270
+ print(f"Size : {f['size']}")
271
+ print(f"Mode : {f['mode'].upper()}")
272
+ print(f"Disorder : {f['disorder']:.2f}%")
273
+ print(f"Reason : {COLORS['RED']}{f['reason']}{COLORS['RESET']}")
274
+ print(f"Operations : {f['ops']} / Limit: {f['limit']}")
275
+ print("-" * 80)
276
+
277
+ # ==========================================
278
+ # MAIN ENTRY
279
+ # ==========================================
280
+ def main():
281
+ if len(sys.argv) < 2 or len(sys.argv) > 4:
282
+ print(f"Usage:")
283
+ print(f" Full Test Suite : {sys.argv[0]} <path_to_push_swap>")
284
+ print(f" Specific Test : {sys.argv[0]} <path_to_push_swap> <size> <mode>")
285
+ sys.exit(1)
286
+
287
+ executable = sys.argv[1]
288
+ if not os.path.isfile(executable) or not os.access(executable, os.X_OK):
289
+ print(f"Error: '{executable}' not found or not executable.")
290
+ sys.exit(1)
291
+
292
+ all_failures = []
293
+ results = []
294
+
295
+ if len(sys.argv) == 2:
296
+ print(f"{COLORS['BOLD']}Running FULL TEST SUITE for {executable}{COLORS['RESET']}\n")
297
+ sizes = [100, 500]
298
+ modes = ["simple", "medium", "complex", "adaptive"]
299
+
300
+ for size in sizes:
301
+ for mode in modes:
302
+ stats, fails = run_test_suite(executable, size, mode)
303
+ results.append(stats)
304
+ all_failures.extend(fails)
305
+
306
+ else:
307
+ try:
308
+ size = int(sys.argv[2])
309
+ except ValueError:
310
+ print("Error: Size must be an integer.")
311
+ sys.exit(1)
312
+
313
+ mode = sys.argv[3].lower() if len(sys.argv) == 4 else "adaptive"
314
+ if mode not in MODES:
315
+ print(f"Error: Invalid mode. Choose from {list(MODES.keys())}")
316
+ sys.exit(1)
317
+
318
+ print(f"{COLORS['BOLD']}Running SINGLE TEST SUITE for {executable}{COLORS['RESET']}\n")
319
+ stats, fails = run_test_suite(executable, size, mode)
320
+ results.append(stats)
321
+ all_failures.extend(fails)
322
+
323
+ # Print detailed failures if any exist
324
+ print_failures(all_failures)
325
+
326
+ # Print global summary
327
+ print("\n" + "="*88)
328
+ print(f"{COLORS['BOLD']}PERFORMANCE SUMMARY{COLORS['RESET']}")
329
+ print("="*88)
330
+ print(f"{'SIZE':<6} | {'MODE':<8} | {'MAX (GRADE)':<18} | {'MIN (GRADE)':<18} | {'AVG (GRADE)':<18} | {'FAILS'}")
331
+ print("-" * 88)
332
+
333
+ for r in results:
334
+ # Get grade info (text and color)
335
+ max_text, max_color = get_grade_info(r['size'], r['max'])
336
+ min_text, min_color = get_grade_info(r['size'], r['min'])
337
+ avg_text, avg_color = get_grade_info(r['size'], r['avg'])
338
+
339
+ # Format columns properly to align ignoring ansi color codes
340
+ col_max = format_grade_column(r['max'], max_text, max_color)
341
+ col_min = format_grade_column(r['min'], min_text, min_color)
342
+ col_avg = format_grade_column(r['avg'], avg_text, avg_color)
343
+
344
+ fail_str = f"{COLORS['RED']}{r['fails']}{COLORS['RESET']}" if r['fails'] > 0 else f"{COLORS['GREEN']}0{COLORS['RESET']}"
345
+
346
+ print(f"{r['size']:<6} | {r['mode'].upper():<8} | {col_max} | {col_min} | {col_avg} | {fail_str}")
347
+
348
+ print("="*88)
349
+
350
+
351
+ if __name__ == "__main__":
352
+ main()
353
+
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: ft_ps_tester
3
+ Version: 1.0.0
4
+ Summary: A Python tester for the 42 push_swap project with controlled disorder generation and performance grading.
5
+ Author: Italo Almeida
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/italoalmeida0/ft_ps_tester
8
+ Project-URL: Issues, https://github.com/italoalmeida0/ft_ps_tester/issues
9
+ Keywords: 42,push_swap,tester,sorting,algorithm
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.7
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Education :: Testing
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Requires-Python: >=3.7
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Dynamic: license-file
26
+
27
+ # ft_ps_tester
28
+
29
+ A Python-based tester for the **42 push_swap** project. It generates controlled random sequences with specific disorder levels, runs your `push_swap` executable, validates the output, and grades performance against 42 thresholds.
30
+
31
+ ---
32
+
33
+ ## Features
34
+
35
+ - **Controlled disorder generation** — creates sequences with precise inversion percentages.
36
+ - **Four test modes** — simple, medium, complex, and adaptive.
37
+ - **Output validation** — simulates operations to verify sorting correctness.
38
+ - **Performance grading** — compares operation counts against 42 thresholds (excellent / good / pass / fail).
39
+ - **Failure report** — concise summary of timeouts, invalid operations, and limit exceedances.
40
+
41
+ ---
42
+
43
+ ## Requirements
44
+
45
+ - Python 3
46
+ - A compiled `push_swap` executable that accepts arguments and prints operations to `stdout`
47
+
48
+ ---
49
+
50
+ ## Installation
51
+
52
+ ### Option 1: Install from PyPI (recommended)
53
+
54
+ ```bash
55
+ pip install ft_ps_tester
56
+ ```
57
+
58
+ Then run from anywhere inside your **push_swap** project:
59
+
60
+ ```bash
61
+ ft_ps_tester ./push_swap
62
+ ```
63
+
64
+ ### Option 2: Install from source
65
+
66
+ Clone this repository:
67
+
68
+ ```bash
69
+ git clone https://github.com/italoalmeida0/ft_ps_tester.git
70
+ cd ft_ps_tester
71
+ ```
72
+
73
+ Install in editable / development mode:
74
+
75
+ ```bash
76
+ pip install -e .
77
+ ```
78
+
79
+ Or install normally:
80
+
81
+ ```bash
82
+ pip install .
83
+ ```
84
+
85
+ Make sure your `push_swap` binary is compiled and executable:
86
+
87
+ ```bash
88
+ make
89
+ chmod +x push_swap
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Usage
95
+
96
+ ### Full test suite (recommended)
97
+
98
+ Tests **100** and **500** elements across all four modes (100 tests each):
99
+
100
+ ```bash
101
+ python3 ft_ps_tester.py ./push_swap
102
+ ```
103
+
104
+ ### Single test run
105
+
106
+ Test a specific size and mode:
107
+
108
+ ```bash
109
+ python3 ft_ps_tester.py ./push_swap <size> <mode>
110
+ ```
111
+
112
+ Example:
113
+
114
+ ```bash
115
+ python3 ft_ps_tester.py ./push_swap 500 complex
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Modes / Flags
121
+
122
+ Your `push_swap` must support the following flags (passed as `--<mode>` before the numbers):
123
+
124
+ | Mode | Disorder range | Description |
125
+ |-----------|----------------|------------------------------------------|
126
+ | `simple` | 15.0% – 19.9% | Nearly sorted sequences |
127
+ | `medium` | 20.0% – 49.9% | Moderately shuffled sequences |
128
+ | `complex` | 50.0% – 55.0% | Heavily shuffled sequences |
129
+ | `adaptive`| 15.0% – 55.0% | Random disorder across the full spectrum |
130
+
131
+ > **Note:** If your `push_swap` does **not** implement these flags, the tester will still work if your program ignores unknown flags and simply sorts the provided numbers. However, for accurate mode-based testing, your `push_swap` should parse and use the flag to adjust its algorithm.
132
+
133
+ ---
134
+
135
+ ## Grading Thresholds
136
+
137
+ | Size | Excellent | Good | Pass |
138
+ |------|-----------|-------|-------|
139
+ | 100 | < 700 | < 1500| ≤ 2000|
140
+ | 500 | < 5500 | < 8000| ≤ 12000|
141
+
142
+ Results are shown with color-coded grades:
143
+ - **EXCELLENT** — green
144
+ - **GOOD** — blue
145
+ - **PASS** — yellow
146
+ - **FAIL** — red
147
+
148
+ ---
149
+
150
+ ## Example Output
151
+
152
+ ```
153
+ Running FULL TEST SUITE for ./push_swap
154
+
155
+ >> Testing Size: 100 | Mode: SIMPLE ..................................................
156
+ >> Testing Size: 100 | Mode: MEDIUM ..................................................
157
+ >> Testing Size: 100 | Mode: COMPLEX ..................................................
158
+ >> Testing Size: 100 | Mode: ADAPTIVE..................................................
159
+ >> Testing Size: 500 | Mode: SIMPLE ..................................................
160
+ >> Testing Size: 500 | Mode: MEDIUM ..................................................
161
+ >> Testing Size: 500 | Mode: COMPLEX ..................................................
162
+ >> Testing Size: 500 | Mode: ADAPTIVE..................................................
163
+
164
+ ========================================================================================
165
+ PERFORMANCE SUMMARY
166
+ ========================================================================================
167
+ SIZE | MODE | MAX (GRADE) | MIN (GRADE) | AVG (GRADE) | FAILS
168
+ ----------------------------------------------------------------------------------------
169
+ 100 | SIMPLE | 450 (EXCELLENT) | 320 (EXCELLENT) | 380 (EXCELLENT) | 0
170
+ 100 | MEDIUM | 1200 (GOOD) | 900 (EXCELLENT) | 1050 (GOOD) | 0
171
+ ...
172
+ ```
173
+
174
+ ---
175
+
176
+ ## License
177
+
178
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ ft_ps_tester/__init__.py
5
+ ft_ps_tester/cli.py
6
+ ft_ps_tester.egg-info/PKG-INFO
7
+ ft_ps_tester.egg-info/SOURCES.txt
8
+ ft_ps_tester.egg-info/dependency_links.txt
9
+ ft_ps_tester.egg-info/entry_points.txt
10
+ ft_ps_tester.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ft_ps_tester = ft_ps_tester.cli:main
@@ -0,0 +1 @@
1
+ ft_ps_tester
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ft_ps_tester"
7
+ version = "1.0.0"
8
+ description = "A Python tester for the 42 push_swap project with controlled disorder generation and performance grading."
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.7"
12
+ authors = [
13
+ {name = "Italo Almeida"}
14
+ ]
15
+ keywords = ["42", "push_swap", "tester", "sorting", "algorithm"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.7",
22
+ "Programming Language :: Python :: 3.8",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Topic :: Education :: Testing",
28
+ "Topic :: Software Development :: Testing",
29
+ ]
30
+
31
+ [project.scripts]
32
+ ft_ps_tester = "ft_ps_tester.cli:main"
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/italoalmeida0/ft_ps_tester"
36
+ Issues = "https://github.com/italoalmeida0/ft_ps_tester/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+