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.
- ft_ps_tester-1.0.0/LICENSE +21 -0
- ft_ps_tester-1.0.0/PKG-INFO +178 -0
- ft_ps_tester-1.0.0/README.md +152 -0
- ft_ps_tester-1.0.0/ft_ps_tester/__init__.py +3 -0
- ft_ps_tester-1.0.0/ft_ps_tester/cli.py +353 -0
- ft_ps_tester-1.0.0/ft_ps_tester.egg-info/PKG-INFO +178 -0
- ft_ps_tester-1.0.0/ft_ps_tester.egg-info/SOURCES.txt +10 -0
- ft_ps_tester-1.0.0/ft_ps_tester.egg-info/dependency_links.txt +1 -0
- ft_ps_tester-1.0.0/ft_ps_tester.egg-info/entry_points.txt +2 -0
- ft_ps_tester-1.0.0/ft_ps_tester.egg-info/top_level.txt +1 -0
- ft_ps_tester-1.0.0/pyproject.toml +36 -0
- ft_ps_tester-1.0.0/setup.cfg +4 -0
|
@@ -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,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 @@
|
|
|
1
|
+
|
|
@@ -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"
|