//! Integration tests for the proc-macro-srv-cli main loop. //! //! These tests exercise the full client-server RPC procedure using in-memory //! channels without needing to spawn the actual server and client processes. #![cfg(feature = "sysroot-abi")] #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] #[cfg(feature = "in-rust-tree")] extern crate rustc_driver as _; mod common { pub(crate) mod utils; } use common::utils::{ create_empty_token_tree, proc_macro_test_dylib_path, request_legacy, with_server, }; use expect_test::expect; use proc_macro_api::{ ProtocolFormat::JsonLegacy, legacy_protocol::msg::{ ExpandMacro, ExpandMacroData, ExpnGlobals, PanicMessage, Request, Response, ServerConfig, SpanDataIndexMap, SpanMode, }, version::CURRENT_API_VERSION, }; #[test] fn test_version_check() { with_server(JsonLegacy, |writer, reader| { let response = request_legacy(writer, reader, Request::ApiVersionCheck {}); match response { Response::ApiVersionCheck(version) => { assert_eq!(version, CURRENT_API_VERSION); } other => panic!("unexpected response: {other:?}"), } }); } #[test] fn test_list_macros() { with_server(JsonLegacy, |writer, reader| { let dylib_path = proc_macro_test_dylib_path(); let response = request_legacy(writer, reader, Request::ListMacros { dylib_path }); let Response::ListMacros(Ok(macros)) = response else { panic!("expected successful ListMacros response"); }; let mut macro_list: Vec<_> = macros.iter().map(|(name, kind)| format!("{name} [{kind:?}]")).collect(); macro_list.sort(); let macro_list_str = macro_list.join("\n"); expect![[r#" DeriveEmpty [CustomDerive] DeriveError [CustomDerive] DerivePanic [CustomDerive] DeriveReemit [CustomDerive] attr_error [Attr] attr_noop [Attr] attr_panic [Attr] fn_like_clone_tokens [Bang] fn_like_error [Bang] fn_like_mk_idents [Bang] fn_like_mk_literals [Bang] fn_like_noop [Bang] fn_like_panic [Bang] fn_like_span_join [Bang] fn_like_span_line_column [Bang] fn_like_span_ops [Bang]"#]] .assert_eq(¯o_list_str); }); } #[test] fn test_list_macros_invalid_path() { with_server(JsonLegacy, |writer, reader| { let response = request_legacy( writer, reader, Request::ListMacros { dylib_path: "/nonexistent/path/to/dylib.so".into() }, ); match response { Response::ListMacros(Err(e)) => assert!( e.starts_with("Cannot create expander for /nonexistent/path/to/dylib.so"), "{e}" ), other => panic!("expected error response, got: {other:?}"), } }); } #[test] fn test_set_config() { with_server(JsonLegacy, |writer, reader| { let config = ServerConfig { span_mode: SpanMode::Id }; let response = request_legacy(writer, reader, Request::SetConfig(config)); match response { Response::SetConfig(returned_config) => { assert_eq!(returned_config.span_mode, SpanMode::Id); } other => panic!("unexpected response: {other:?}"), } }); } #[test] fn test_set_config_rust_analyzer_mode() { with_server(JsonLegacy, |writer, reader| { let config = ServerConfig { span_mode: SpanMode::RustAnalyzer }; let response = request_legacy(writer, reader, Request::SetConfig(config)); match response { Response::SetConfig(returned_config) => { assert_eq!(returned_config.span_mode, SpanMode::RustAnalyzer); } other => panic!("unexpected response: {other:?}"), } }); } #[test] fn test_expand_macro_panic() { with_server(JsonLegacy, |writer, reader| { let dylib_path = proc_macro_test_dylib_path(); let version_response = request_legacy(writer, reader, Request::ApiVersionCheck {}); let Response::ApiVersionCheck(version) = version_response else { panic!("expected version check response"); }; let mut span_data_table = SpanDataIndexMap::default(); let macro_body = create_empty_token_tree(version, &mut span_data_table); let expand_request = Request::ExpandMacro(Box::new(ExpandMacro { lib: dylib_path, env: vec![], current_dir: None, data: ExpandMacroData { macro_body, macro_name: "fn_like_panic".to_owned(), attributes: None, has_global_spans: ExpnGlobals { serialize: version >= 3, def_site: 0, call_site: 0, mixed_site: 0, }, span_data_table: vec![], }, })); let response = request_legacy(writer, reader, expand_request); match response { Response::ExpandMacro(Err(PanicMessage(msg))) => { assert!(msg.contains("fn_like_panic"), "panic message should mention the macro"); } Response::ExpandMacro(Ok(_)) => { panic!("expected panic, but macro succeeded"); } other => panic!("unexpected response: {other:?}"), } }); } #[test] fn test_basic_call_flow() { with_server(JsonLegacy, |writer, reader| { let dylib_path = proc_macro_test_dylib_path(); let response1 = request_legacy(writer, reader, Request::ApiVersionCheck {}); assert!(matches!(response1, Response::ApiVersionCheck(_))); let response2 = request_legacy( writer, reader, Request::SetConfig(ServerConfig { span_mode: SpanMode::Id }), ); assert!(matches!(response2, Response::SetConfig(_))); let response3 = request_legacy(writer, reader, Request::ListMacros { dylib_path: dylib_path.clone() }); assert!(matches!(response3, Response::ListMacros(Ok(_)))); }); } #[test] fn test_expand_nonexistent_macro() { with_server(JsonLegacy, |writer, reader| { let dylib_path = proc_macro_test_dylib_path(); let version_response = request_legacy(writer, reader, Request::ApiVersionCheck {}); let Response::ApiVersionCheck(version) = version_response else { panic!("expected version check response"); }; let mut span_data_table = SpanDataIndexMap::default(); let macro_body = create_empty_token_tree(version, &mut span_data_table); let expand_request = Request::ExpandMacro(Box::new(ExpandMacro { lib: dylib_path, env: vec![], current_dir: None, data: ExpandMacroData { macro_body, macro_name: "NonexistentMacro".to_owned(), attributes: None, has_global_spans: ExpnGlobals { serialize: version >= 3, def_site: 0, call_site: 0, mixed_site: 0, }, span_data_table: vec![], }, })); let response = request_legacy(writer, reader, expand_request); match response { Response::ExpandMacro(Err(PanicMessage(msg))) => { expect!["proc-macro `NonexistentMacro` is missing"].assert_eq(&msg) } other => panic!("expected error for nonexistent macro, got: {other:?}"), } }); }