Skip to content

Commit 7d42ebb

Browse files
bors[bot]Bromeon
andauthored
Merge #146
146: `#[itest(skip)]` + `#[itest(focus)]` r=Bromeon a=Bromeon Follow-up to #142. Two new test features: * `#[itest(skip)]` ignores the current test, and shows the number of skipped tests in a statistic at the end of the run. * This is better than empty tests with `// TODO` or commented-out tests, as it reminds you of the technical debt and gives an impression of its extents. * If at least one test is annotated with `#[itest(focus)]`, then _only_ "focused" tests are run. * Extremely helpful during debugging sessions, or when one is working on a very particular feature. * If Godot is invoked with `-- --disallow-focus`, focus runs will always fail (for CI, to avoid accidental disabling of tests). This is not yet implemented for GDScript. Might be worth it, or might not. Apart from that, this PR massively simplifies the internal APIs to parse `#[attribute(key, key2="value")]` attributes. Co-authored-by: Jan Haller <[email protected]>
2 parents 55c4d3a + 1a4a47a commit 7d42ebb

File tree

19 files changed

+392
-241
lines changed

19 files changed

+392
-241
lines changed

.github/composite/godot-install/action.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ inputs:
1010
required: true
1111
description: "Name of the compiled Godot artifact to download"
1212

13-
binary-filename:
13+
godot-binary:
1414
required: true
1515
description: "Filename of the Godot executable"
1616

@@ -25,7 +25,7 @@ runs:
2525
run: |
2626
runnerDir=$(echo "${{ runner.temp }}" | sed "s!\\\\!/!")
2727
echo "RUNNER_DIR=$runnerDir" >> $GITHUB_ENV
28-
echo "GODOT4_BIN=$runnerDir/godot_bin/${{ inputs.binary-filename }}" >> $GITHUB_ENV
28+
echo "GODOT4_BIN=$runnerDir/godot_bin/${{ inputs.godot-binary }}" >> $GITHUB_ENV
2929
shell: bash
3030

3131
# - name: "Check cache for installed Godot version"

.github/composite/godot-itest/action.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@ inputs:
1010
required: true
1111
description: "Name of the compiled Godot artifact to download"
1212

13-
binary-filename:
13+
godot-binary:
1414
required: true
1515
description: "Filename of the Godot executable"
1616

17+
godot-args:
18+
required: false
19+
default: ''
20+
description: "Command-line arguments passed to Godot"
21+
1722
rust-toolchain:
1823
required: false
1924
default: 'stable'
@@ -26,8 +31,8 @@ inputs:
2631

2732
with-llvm:
2833
required: false
29-
description: "Set to 'true' if LLVM should be installed"
3034
default: ''
35+
description: "Set to 'true' if LLVM should be installed"
3136

3237

3338
runs:
@@ -39,7 +44,7 @@ runs:
3944
uses: ./.github/composite/godot-install
4045
with:
4146
artifact-name: ${{ inputs.artifact-name }}
42-
binary-filename: ${{ inputs.binary-filename }}
47+
godot-binary: ${{ inputs.godot-binary }}
4348

4449
# The chmod seems still necessary, although applied before uploading artifact. Possibly modes are not preserved.
4550
# The `| xargs` pattern trims the output, since printed version may contain extra newline, which causes problems in env vars.
@@ -94,7 +99,7 @@ runs:
9499
run: |
95100
cd itest/godot
96101
echo "OUTCOME=itest" >> $GITHUB_ENV
97-
$GODOT4_BIN --headless 2>&1 | tee "${{ runner.temp }}/log.txt" | tee >(grep "SCRIPT ERROR:" -q && {
102+
$GODOT4_BIN --headless ${{ inputs.godot-args }} 2>&1 | tee "${{ runner.temp }}/log.txt" | tee >(grep "SCRIPT ERROR:" -q && {
98103
printf "\n -- Godot engine encountered error, abort...\n";
99104
pkill godot
100105
echo "OUTCOME=godot-runtime" >> $GITHUB_ENV

.github/workflows/full-ci.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ jobs:
5757
uses: ./.github/composite/godot-install
5858
with:
5959
artifact-name: godot-linux
60-
binary-filename: godot.linuxbsd.editor.dev.x86_64
60+
godot-binary: godot.linuxbsd.editor.dev.x86_64
6161

6262
- name: "Check clippy"
6363
run: |
@@ -126,7 +126,7 @@ jobs:
126126
uses: ./.github/composite/godot-install
127127
with:
128128
artifact-name: godot-${{ matrix.name }}
129-
binary-filename: ${{ matrix.godot-binary }}
129+
godot-binary: ${{ matrix.godot-binary }}
130130

131131
- name: "Compile tests"
132132
run: cargo test $GDEXT_FEATURES --no-run
@@ -168,15 +168,19 @@ jobs:
168168
# Additionally, the Godot source is patched to make dlclose() a no-op, as unloading dynamic libraries loses stacktrace and
169169
# cause false positives like println!. See https://github.com/google/sanitizers/issues/89.
170170
# The gcc version can possibly be removed later, as it is slower and needs a larger artifact than the clang one.
171+
172+
# --disallow-focus: fail if #[itest(focus)] is encountered, to prevent running only a few tests for full CI
171173
- name: linux-memcheck-gcc
172174
os: ubuntu-20.04
173175
rust-toolchain: stable
174176
godot-binary: godot.linuxbsd.editor.dev.x86_64.san
177+
godot-args: -- --disallow-focus
175178

176179
- name: linux-memcheck-clang
177180
os: ubuntu-20.04
178181
rust-toolchain: stable
179182
godot-binary: godot.linuxbsd.editor.dev.x86_64.llvm.san
183+
godot-args: -- --disallow-focus
180184

181185
steps:
182186
- uses: actions/checkout@v3
@@ -185,7 +189,8 @@ jobs:
185189
uses: ./.github/composite/godot-itest
186190
with:
187191
artifact-name: godot-${{ matrix.name }}
188-
binary-filename: ${{ matrix.godot-binary }}
192+
godot-binary: ${{ matrix.godot-binary }}
193+
godot-args: ${{ matrix.godot-args }}
189194
with-llvm: ${{ matrix.name == 'macos' }}
190195

191196

.github/workflows/minimal-ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ jobs:
5757
uses: ./.github/composite/godot-install
5858
with:
5959
artifact-name: godot-linux
60-
binary-filename: godot.linuxbsd.editor.dev.x86_64
60+
godot-binary: godot.linuxbsd.editor.dev.x86_64
6161

6262
- name: "Check clippy"
6363
run: |
@@ -78,7 +78,7 @@ jobs:
7878
uses: ./.github/composite/godot-install
7979
with:
8080
artifact-name: godot-linux
81-
binary-filename: godot.linuxbsd.editor.dev.x86_64
81+
godot-binary: godot.linuxbsd.editor.dev.x86_64
8282

8383
- name: "Compile tests"
8484
run: cargo test $GDEXT_FEATURES --no-run
@@ -98,7 +98,7 @@ jobs:
9898
uses: ./.github/composite/godot-itest
9999
with:
100100
artifact-name: godot-linux
101-
binary-filename: godot.linuxbsd.editor.dev.x86_64
101+
godot-binary: godot.linuxbsd.editor.dev.x86_64
102102
#godot_ver: ${{ matrix.godot }}
103103

104104

godot-ffi/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ pub fn default_call_error() -> GDExtensionCallError {
151151

152152
#[doc(hidden)]
153153
#[inline]
154+
#[track_caller] // panic message points to call site
154155
pub fn panic_on_call_error(err: &GDExtensionCallError) {
155156
let actual = err.error;
156157

godot-macros/src/derive_godot_class.rs

Lines changed: 38 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,13 @@
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
66

7-
use crate::util::{bail, ensure_kv_empty, ident, parse_kv_group, path_is_single, KvMap, KvValue};
8-
use crate::{util, ParseResult};
9-
use proc_macro2::{Ident, Punct, Span, TokenStream};
10-
use quote::spanned::Spanned;
7+
use crate::util::{bail, ident, KvParser};
8+
use crate::ParseResult;
9+
use proc_macro2::{Ident, Punct, TokenStream};
1110
use quote::{format_ident, quote};
12-
use venial::{Attribute, NamedField, Struct, StructFields, TyExpr};
13-
14-
pub fn transform(input: TokenStream) -> ParseResult<TokenStream> {
15-
let decl = venial::parse_declaration(input)?;
11+
use venial::{Declaration, NamedField, Struct, StructFields, TyExpr};
1612

13+
pub fn transform(decl: Declaration) -> ParseResult<TokenStream> {
1714
let class = decl
1815
.as_struct()
1916
.ok_or_else(|| venial::Error::new("Not a valid struct"))?;
@@ -68,43 +65,24 @@ pub fn transform(input: TokenStream) -> ParseResult<TokenStream> {
6865

6966
/// Returns the name of the base and the default mode
7067
fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
71-
let mut base = ident("RefCounted");
72-
//let mut new_mode = GodotConstructMode::AutoGenerated;
68+
let mut base_ty = ident("RefCounted");
7369
let mut has_generated_init = false;
7470

7571
// #[func] attribute on struct
76-
if let Some((span, mut map)) = parse_class_attr(&class.attributes)? {
77-
//println!(">>> CLASS {class}, MAP: {map:?}", class = class.name);
78-
79-
if let Some(kv_value) = map.remove("base") {
80-
if let KvValue::Ident(override_base) = kv_value {
81-
base = override_base;
82-
} else {
83-
bail("Invalid value for 'base' argument", span)?;
84-
}
72+
if let Some(mut parser) = KvParser::parse(&class.attributes, "class")? {
73+
if let Some(base) = parser.handle_ident("base")? {
74+
base_ty = base;
8575
}
8676

87-
/*if let Some(kv_value) = map.remove("new") {
88-
match kv_value {
89-
KvValue::Ident(ident) if ident == "fn" => new_mode = GodotConstructMode::FnNew,
90-
KvValue::Ident(ident) if ident == "none" => new_mode = GodotConstructMode::None,
91-
_ => bail(
92-
"Invalid value for 'new' argument; must be 'fn' or 'none'",
93-
span,
94-
)?,
95-
}
96-
}*/
97-
if let Some(kv_value) = map.remove("init") {
98-
match kv_value {
99-
KvValue::None => has_generated_init = true,
100-
_ => bail("Argument 'init' must not have a value", span)?,
101-
}
77+
if parser.handle_alone("init")? {
78+
has_generated_init = true;
10279
}
103-
ensure_kv_empty(map, span)?;
80+
81+
parser.finish()?;
10482
}
10583

10684
Ok(ClassAttributes {
107-
base_ty: base,
85+
base_ty,
10886
has_generated_init,
10987
})
11088
}
@@ -130,34 +108,27 @@ fn parse_fields(class: &Struct) -> ParseResult<Fields> {
130108
for (field, _punct) in fields {
131109
let mut is_base = false;
132110

133-
// #[base] or #[export]
134-
for attr in field.attributes.iter() {
135-
if let Some(path) = attr.get_single_path_segment() {
136-
if path == "base" {
137-
is_base = true;
138-
if let Some(prev_base) = base_field {
139-
bail(
140-
format!(
141-
"#[base] allowed for at most 1 field, already applied to '{}'",
142-
prev_base.name
143-
),
144-
attr,
145-
)?;
146-
}
147-
base_field = Some(Field::new(&field))
148-
} else if path == "export" {
149-
match parse_kv_group(&attr.value) {
150-
Ok(export_kv) => {
151-
let exported_field =
152-
ExportedField::new_from_kv(Field::new(&field), attr, export_kv)?;
153-
exported_fields.push(exported_field);
154-
}
155-
Err(error) => {
156-
return Err(error);
157-
}
158-
}
159-
}
111+
// #[base]
112+
if let Some(parser) = KvParser::parse(&field.attributes, "base")? {
113+
if let Some(prev_base) = base_field {
114+
bail(
115+
format!(
116+
"#[base] allowed for at most 1 field, already applied to '{}'",
117+
prev_base.name
118+
),
119+
parser.span(),
120+
)?;
160121
}
122+
is_base = true;
123+
base_field = Some(Field::new(&field));
124+
parser.finish()?;
125+
}
126+
127+
// #[export]
128+
if let Some(mut parser) = KvParser::parse(&field.attributes, "export")? {
129+
let exported_field = ExportedField::new_from_kv(Field::new(&field), &mut parser)?;
130+
exported_fields.push(exported_field);
131+
parser.finish()?;
161132
}
162133

163134
// Exported or Rust-only fields
@@ -173,26 +144,6 @@ fn parse_fields(class: &Struct) -> ParseResult<Fields> {
173144
})
174145
}
175146

176-
/// Parses a `#[class(...)]` attribute
177-
fn parse_class_attr(attributes: &[Attribute]) -> ParseResult<Option<(Span, KvMap)>> {
178-
let mut godot_attr = None;
179-
for attr in attributes.iter() {
180-
let path = &attr.path;
181-
if path_is_single(path, "class") {
182-
if godot_attr.is_some() {
183-
bail(
184-
"Only one #[class] attribute per item (struct, fn, ...) allowed",
185-
attr,
186-
)?;
187-
}
188-
189-
let map = util::parse_kv_group(&attr.value)?;
190-
godot_attr = Some((attr.__span(), map));
191-
}
192-
}
193-
Ok(godot_attr)
194-
}
195-
196147
// ----------------------------------------------------------------------------------------------------------------------------------------------
197148
// General helpers
198149

@@ -229,16 +180,10 @@ struct ExportedField {
229180
}
230181

231182
impl ExportedField {
232-
pub fn new_from_kv(
233-
field: Field,
234-
attr: &Attribute,
235-
mut map: KvMap,
236-
) -> ParseResult<ExportedField> {
237-
let getter = Self::require_key_value(&mut map, "getter", attr)?;
238-
let setter = Self::require_key_value(&mut map, "setter", attr)?;
239-
let variant_type = Self::require_key_value(&mut map, "variant_type", attr)?;
240-
241-
ensure_kv_empty(map, attr.__span())?;
183+
pub fn new_from_kv(field: Field, parser: &mut KvParser) -> ParseResult<ExportedField> {
184+
let getter = parser.handle_lit_required("getter")?;
185+
let setter = parser.handle_lit_required("setter")?;
186+
let variant_type = parser.handle_lit_required("variant_type")?;
242187

243188
Ok(ExportedField {
244189
field,
@@ -247,21 +192,6 @@ impl ExportedField {
247192
variant_type,
248193
})
249194
}
250-
251-
fn require_key_value(map: &mut KvMap, key: &str, attr: &Attribute) -> ParseResult<String> {
252-
if let Some(value) = map.remove(key) {
253-
if let KvValue::Lit(value) = value {
254-
Ok(value)
255-
} else {
256-
bail(
257-
format!("#[export] attribute {key} with a non-literal variant_type",),
258-
attr,
259-
)?
260-
}
261-
} else {
262-
bail(format!("#[export] attribute without a {key}"), attr)
263-
}
264-
}
265195
}
266196

267197
fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream {

godot-macros/src/gdextension.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,7 @@ use proc_macro2::TokenStream;
99
use quote::quote;
1010
use venial::Declaration;
1111

12-
pub fn transform(meta: TokenStream, input: TokenStream) -> Result<TokenStream, venial::Error> {
13-
// Hack because venial doesn't support direct meta parsing yet
14-
let input = quote! {
15-
#[gdextension(#meta)]
16-
#input
17-
};
18-
19-
let decl = venial::parse_declaration(input)?;
20-
12+
pub fn transform(decl: Declaration) -> Result<TokenStream, venial::Error> {
2113
let mut impl_decl = match decl {
2214
Declaration::Impl(item) => item,
2315
_ => return bail("#[gdextension] can only be applied to trait impls", &decl),

godot-macros/src/godot_api.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ use venial::{AttributeValue, Declaration, Error, Function, Impl, ImplMember};
1313
// Note: keep in sync with trait GodotExt
1414
const VIRTUAL_METHOD_NAMES: [&str; 3] = ["ready", "process", "physics_process"];
1515

16-
pub fn transform(input: TokenStream) -> Result<TokenStream, Error> {
17-
let input_decl = venial::parse_declaration(input)?;
16+
pub fn transform(input_decl: Declaration) -> Result<TokenStream, Error> {
1817
let decl = match input_decl {
1918
Declaration::Impl(decl) => decl,
2019
_ => bail(

0 commit comments

Comments
 (0)