Skip to content

Commit b255cbe

Browse files
authored
Merge pull request #18 from openai/dev/scl/better-ci
better ci
2 parents 058ef32 + 3c06d6f commit b255cbe

File tree

6 files changed

+162
-62
lines changed

6 files changed

+162
-62
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: Run Rust and Python tests
2+
3+
description: Format, clippy, Rust tests (incl. doctests), build Python extension and run pytest
4+
5+
inputs:
6+
python-version:
7+
description: Python version to use
8+
required: false
9+
default: "3.11"
10+
rust-toolchain:
11+
description: Rust toolchain channel
12+
required: false
13+
default: stable
14+
15+
runs:
16+
using: composite
17+
steps:
18+
- name: Setup Rust toolchain
19+
uses: dtolnay/rust-toolchain@stable
20+
with:
21+
toolchain: ${{ inputs.rust-toolchain }}
22+
components: clippy,rustfmt
23+
24+
- name: Setup Python
25+
uses: actions/setup-python@v5
26+
with:
27+
python-version: ${{ inputs.python-version }}
28+
29+
- name: Upgrade pip
30+
shell: bash
31+
run: |
32+
python -m pip install --upgrade pip
33+
34+
- name: Install test deps
35+
shell: bash
36+
run: |
37+
python -m pip install pytest
38+
39+
- name: Check rustfmt
40+
shell: bash
41+
run: |
42+
cargo fmt --all --check
43+
44+
- name: Run clippy
45+
shell: bash
46+
run: |
47+
cargo clippy --all-targets --all-features -- -D warnings
48+
49+
- name: Run Rust tests (unit/integration)
50+
shell: bash
51+
run: |
52+
cargo test --all-targets --all-features
53+
54+
- name: Run Rust doctests
55+
shell: bash
56+
run: |
57+
cargo test --doc
58+
59+
- name: Build and install Python package
60+
shell: bash
61+
run: |
62+
pip install .
63+
64+
- name: Run pytest
65+
shell: bash
66+
env:
67+
PYTHONUTF8: "1"
68+
run: |
69+
pytest -q

.github/workflows/CI.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,23 @@ permissions:
1919
contents: read
2020

2121
jobs:
22+
tests:
23+
name: Tests (fmt, clippy, cargo test, doctest, pytest)
24+
runs-on: ${{ matrix.os }}
25+
strategy:
26+
fail-fast: false
27+
matrix:
28+
os: [ubuntu-latest, macos-14, windows-latest]
29+
steps:
30+
- uses: actions/checkout@v4
31+
- name: Run composite test suite
32+
uses: ./.github/actions/run-rust-python-tests
33+
with:
34+
python-version: "3.11"
35+
rust-toolchain: stable
36+
2237
linux:
38+
needs: [tests]
2339
runs-on: ${{ matrix.platform.runner }}
2440
strategy:
2541
matrix:
@@ -54,6 +70,7 @@ jobs:
5470
path: dist
5571

5672
musllinux:
73+
needs: [tests]
5774
runs-on: ${{ matrix.platform.runner }}
5875
strategy:
5976
matrix:
@@ -88,6 +105,7 @@ jobs:
88105
path: dist
89106

90107
windows:
108+
needs: [tests]
91109
runs-on: ${{ matrix.platform.runner }}
92110
strategy:
93111
matrix:
@@ -115,6 +133,7 @@ jobs:
115133
path: dist
116134

117135
macos:
136+
needs: [tests]
118137
runs-on: ${{ matrix.platform.runner }}
119138
strategy:
120139
matrix:
@@ -141,6 +160,7 @@ jobs:
141160
path: dist
142161

143162
sdist:
163+
needs: [tests]
144164
runs-on: ubuntu-latest
145165
steps:
146166
- uses: actions/checkout@v4

src/py_module.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212
//! A thin, typed, user-facing Python wrapper around these low-level bindings is
1313
//! provided in `harmony/__init__.py`.
1414
15-
// Only compile when the `python-binding` feature is enabled.
16-
#![cfg(feature = "python-binding")]
17-
1815
use pyo3::prelude::*;
1916

2017
// We need the `Python` type later on.
@@ -34,8 +31,6 @@ use crate::{
3431
load_harmony_encoding, HarmonyEncodingName,
3532
};
3633

37-
use serde_json;
38-
3934
/// A thin PyO3 wrapper around the Rust `HarmonyEncoding` struct.
4035
#[pyclass]
4136
struct PyHarmonyEncoding {
@@ -393,8 +388,7 @@ fn openai_harmony(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
393388
"python" => ToolNamespaceConfig::python(),
394389
_ => {
395390
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
396-
"Unknown tool namespace: {}",
397-
tool
391+
"Unknown tool namespace: {tool}"
398392
)));
399393
}
400394
};

src/tests.rs

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::path::Path;
2+
13
use crate::{
24
chat::{
35
Author, Conversation, DeveloperContent, Message, ReasoningEffort, Role, SystemContent,
@@ -10,12 +12,25 @@ use crate::{
1012
use pretty_assertions::{assert_eq, Comparison};
1113
use serde_json::json;
1214

13-
fn parse_tokens(text: &str) -> Vec<Rank> {
14-
text.split_whitespace()
15+
fn parse_tokens(text: impl AsRef<str>) -> Vec<Rank> {
16+
text.as_ref()
17+
.split_whitespace()
1518
.map(|s| s.parse().unwrap())
1619
.collect()
1720
}
1821

22+
fn load_test_data(path: impl AsRef<Path>) -> String {
23+
// on windows, we need to replace \r\n with \n
24+
let cargo_manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
25+
let src_dir = cargo_manifest_dir.join("src");
26+
let path = src_dir.join(path);
27+
std::fs::read_to_string(path)
28+
.unwrap()
29+
.replace("\r\n", "\n")
30+
.trim_end()
31+
.to_string()
32+
}
33+
1934
const ENCODINGS: [HarmonyEncodingName; 1] = [HarmonyEncodingName::HarmonyGptOss];
2035

2136
#[test]
@@ -25,7 +40,7 @@ fn test_simple_convo() {
2540
let expected_tokens = encoding
2641
.tokenizer
2742
.encode(
28-
include_str!("../test-data/test_simple_convo.txt").trim_end(),
43+
load_test_data("../test-data/test_simple_convo.txt").as_str(),
2944
&encoding.tokenizer.special_tokens(),
3045
)
3146
.0;
@@ -50,45 +65,42 @@ fn test_simple_convo_with_effort() {
5065
let test_cases = [
5166
(
5267
ReasoningEffort::Low,
53-
include_str!("../test-data/test_simple_convo_low_effort.txt"),
68+
load_test_data("../test-data/test_simple_convo_low_effort.txt"),
5469
true,
5570
),
5671
(
5772
ReasoningEffort::Medium,
58-
include_str!("../test-data/test_simple_convo_medium_effort.txt"),
73+
load_test_data("../test-data/test_simple_convo_medium_effort.txt"),
5974
true,
6075
),
6176
(
6277
ReasoningEffort::High,
63-
include_str!("../test-data/test_simple_convo_high_effort.txt"),
78+
load_test_data("../test-data/test_simple_convo_high_effort.txt"),
6479
true,
6580
),
6681
(
6782
ReasoningEffort::Low,
68-
include_str!("../test-data/test_simple_convo_low_effort_no_instruction.txt"),
83+
load_test_data("../test-data/test_simple_convo_low_effort_no_instruction.txt"),
6984
false,
7085
),
7186
(
7287
ReasoningEffort::Medium,
73-
include_str!("../test-data/test_simple_convo_medium_effort_no_instruction.txt"),
88+
load_test_data("../test-data/test_simple_convo_medium_effort_no_instruction.txt"),
7489
false,
7590
),
7691
(
7792
ReasoningEffort::High,
78-
include_str!("../test-data/test_simple_convo_high_effort_no_instruction.txt"),
93+
load_test_data("../test-data/test_simple_convo_high_effort_no_instruction.txt"),
7994
false,
8095
),
8196
];
8297

8398
for encoding_name in ENCODINGS {
8499
let encoding = load_harmony_encoding(encoding_name).unwrap();
85-
for (effort, expected_text, use_instruction) in test_cases {
100+
for &(effort, ref expected_text, use_instruction) in &test_cases {
86101
let expected_tokens = encoding
87102
.tokenizer
88-
.encode(
89-
expected_text.trim_end(),
90-
&encoding.tokenizer.special_tokens(),
91-
)
103+
.encode(expected_text.as_str(), &encoding.tokenizer.special_tokens())
92104
.0;
93105
let sys = SystemContent::new()
94106
.with_model_identity("You are ChatGPT, a large language model trained by OpenAI.")
@@ -123,8 +135,8 @@ fn test_simple_convo_with_effort() {
123135

124136
#[test]
125137
fn test_simple_reasoning_response() {
126-
let expected_tokens = parse_tokens(include_str!(
127-
"../test-data/test_simple_reasoning_response.txt"
138+
let expected_tokens = parse_tokens(load_test_data(
139+
"../test-data/test_simple_reasoning_response.txt",
128140
));
129141
for encoding_name in ENCODINGS {
130142
let encoding = load_harmony_encoding(encoding_name).unwrap();
@@ -180,7 +192,7 @@ fn test_reasoning_system_message() {
180192
let expected = encoding
181193
.tokenizer
182194
.encode(
183-
include_str!("../test-data/test_reasoning_system_message.txt").trim_end(),
195+
load_test_data("../test-data/test_reasoning_system_message.txt").as_str(),
184196
&encoding.tokenizer.special_tokens(),
185197
)
186198
.0;
@@ -211,8 +223,8 @@ fn test_reasoning_system_message_no_instruction() {
211223
let expected = encoding
212224
.tokenizer
213225
.encode(
214-
include_str!("../test-data/test_reasoning_system_message_no_instruction.txt")
215-
.trim_end(),
226+
load_test_data("../test-data/test_reasoning_system_message_no_instruction.txt")
227+
.as_str(),
216228
&encoding.tokenizer.special_tokens(),
217229
)
218230
.0;
@@ -245,8 +257,8 @@ fn test_reasoning_system_message_with_dates() {
245257
let expected = encoding
246258
.tokenizer
247259
.encode(
248-
include_str!("../test-data/test_reasoning_system_message_with_dates.txt")
249-
.trim_end(),
260+
load_test_data("../test-data/test_reasoning_system_message_with_dates.txt")
261+
.as_str(),
250262
&encoding.tokenizer.special_tokens(),
251263
)
252264
.0;
@@ -275,8 +287,7 @@ fn test_reasoning_system_message_with_dates() {
275287
#[test]
276288
fn test_render_functions_with_parameters() {
277289
let encoding = load_harmony_encoding(HarmonyEncodingName::HarmonyGptOss).unwrap();
278-
let expected_output =
279-
include_str!("../test-data/test_render_functions_with_parameters.txt").trim_end();
290+
let expected_output = load_test_data("../test-data/test_render_functions_with_parameters.txt");
280291

281292
let sys = SystemContent::new()
282293
.with_reasoning_effort(ReasoningEffort::High)
@@ -382,7 +393,7 @@ fn test_render_functions_with_parameters() {
382393
#[test]
383394
fn test_browser_and_python_tool() {
384395
let encoding = load_harmony_encoding(HarmonyEncodingName::HarmonyGptOss).unwrap();
385-
let expected_output = include_str!("../test-data/test_browser_and_python_tool.txt").trim_end();
396+
let expected_output = load_test_data("../test-data/test_browser_and_python_tool.txt");
386397

387398
let convo = Conversation::from_messages([Message::from_role_and_content(
388399
Role::System,
@@ -403,7 +414,7 @@ fn test_browser_and_python_tool() {
403414
#[test]
404415
fn test_dropping_cot_by_default() {
405416
let encoding = load_harmony_encoding(HarmonyEncodingName::HarmonyGptOss).unwrap();
406-
let expected_output = include_str!("../test-data/test_dropping_cot_by_default.txt").trim_end();
417+
let expected_output = load_test_data("../test-data/test_dropping_cot_by_default.txt");
407418

408419
let convo = Conversation::from_messages([
409420
Message::from_role_and_content(Role::User, "What is 2 + 2?"),
@@ -433,8 +444,7 @@ fn test_dropping_cot_by_default() {
433444
#[test]
434445
fn test_does_not_drop_if_ongoing_analysis() {
435446
let encoding = load_harmony_encoding(HarmonyEncodingName::HarmonyGptOss).unwrap();
436-
let expected_output =
437-
include_str!("../test-data/test_does_not_drop_if_ongoing_analysis.txt").trim_end();
447+
let expected_output = load_test_data("../test-data/test_does_not_drop_if_ongoing_analysis.txt");
438448

439449
let convo = Conversation::from_messages([
440450
Message::from_role_and_content(Role::User, "What is the weather in SF?"),
@@ -470,7 +480,7 @@ fn test_does_not_drop_if_ongoing_analysis() {
470480
#[test]
471481
fn test_preserve_cot() {
472482
let encoding = load_harmony_encoding(HarmonyEncodingName::HarmonyGptOss).unwrap();
473-
let expected_output = include_str!("../test-data/test_preserve_cot.txt").trim_end();
483+
let expected_output = load_test_data("../test-data/test_preserve_cot.txt");
474484

475485
let convo = Conversation::from_messages([
476486
Message::from_role_and_content(Role::User, "What is 2 + 2?"),
@@ -534,10 +544,10 @@ fn test_decode_utf8_invalid_token() {
534544
#[test]
535545
fn test_tool_response_parsing() {
536546
let encoding = load_harmony_encoding(HarmonyEncodingName::HarmonyGptOss).unwrap();
537-
let text_tokens = include_str!("../test-data/test_tool_response_parsing.txt").trim_end();
547+
let text_tokens = load_test_data("../test-data/test_tool_response_parsing.txt");
538548
let tokens = encoding
539549
.tokenizer
540-
.encode(text_tokens, &encoding.tokenizer.special_tokens())
550+
.encode(&text_tokens, &encoding.tokenizer.special_tokens())
541551
.0;
542552

543553
let expected_message = Message::from_author_and_content(
@@ -616,10 +626,10 @@ fn test_invalid_utf8_decoding() {
616626
#[test]
617627
fn test_streamable_parser() {
618628
let encoding = load_harmony_encoding(HarmonyEncodingName::HarmonyGptOss).unwrap();
619-
let text = include_str!("../test-data/test_streamable_parser.txt").trim_end();
629+
let text = load_test_data("../test-data/test_streamable_parser.txt");
620630
let tokens = encoding
621631
.tokenizer
622-
.encode(text, &encoding.tokenizer.special_tokens())
632+
.encode(&text, &encoding.tokenizer.special_tokens())
623633
.0;
624634
let mut parser =
625635
crate::encoding::StreamableParser::new(encoding.clone(), Some(Role::Assistant)).unwrap();

src/wasm_module.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#![cfg(feature = "wasm-binding")]
2-
31
use wasm_bindgen::prelude::*;
42

53
use crate::{
@@ -9,8 +7,6 @@ use crate::{
97
};
108

119
use serde::Deserialize;
12-
use serde_json;
13-
use serde_wasm_bindgen;
1410

1511
#[wasm_bindgen]
1612
extern "C" {
@@ -335,8 +331,7 @@ pub fn get_tool_namespace_config(tool: &str) -> Result<JsValue, JsValue> {
335331
"python" => ToolNamespaceConfig::python(),
336332
_ => {
337333
return Err(JsValue::from_str(&format!(
338-
"Unknown tool namespace: {}",
339-
tool
334+
"Unknown tool namespace: {tool}"
340335
)))
341336
}
342337
};

0 commit comments

Comments
 (0)