provola_egui/
lib.rs

1mod app;
2mod server;
3mod tests_explorer;
4
5pub use crate::app::ProvolaGuiApp;
6use crate::server::Server;
7use crossbeam_channel::{bounded, Receiver, Sender};
8use eframe::epi::backend::RepaintSignal;
9use provola_core::test::xunit::FullyQualifiedTestCaseId;
10use provola_core::test_runners::TestRunnerOpt;
11use provola_core::*;
12use provola_core::{Action, AvailableTests, Error, TestResult};
13use provola_testrunners::make_test_runner;
14use provola_testrunners::TestRunnerInfo;
15use std::path::PathBuf;
16use std::{sync::Arc, thread};
17
18struct Setup {
19    config: GuiConfig,
20    repaint_signal: Arc<dyn RepaintSignal>,
21}
22
23enum ActionMessage {
24    Setup(Setup),
25    RunAll,
26    RunSelected,
27    UpdateConfig(GuiConfig),
28    ReqAvailableTests,
29    SelectSingleTest(FullyQualifiedTestCaseId),
30}
31
32enum FeedbackMessage {
33    AvailableTests(AvailableTests),
34    Result(TestResult),
35    WatchedChanged,
36    Error(String),
37}
38
39type ActionSender = Sender<ActionMessage>;
40type ActionReceiver = Receiver<ActionMessage>;
41type FeedbackReceiver = Receiver<FeedbackMessage>;
42
43// When the server send a feedback to the gui, the gui must awake.
44// To do that, we use repaint_signal to notify the gui whenever a
45// feedback is sent.
46#[derive(Clone)]
47struct FeedbackSender {
48    sender: Sender<FeedbackMessage>,
49    repaint_signal: Option<Arc<dyn RepaintSignal>>,
50}
51
52impl FeedbackSender {
53    fn send(&self, msg: FeedbackMessage) {
54        if let Err(err) = self.sender.send(msg) {
55            // Ignore this error, usually happens on exit
56            log::debug!("Error ignored: {}", err);
57        }
58
59        if let Some(repaint_signal) = &self.repaint_signal {
60            repaint_signal.request_repaint();
61        }
62    }
63}
64
65pub fn run(opt: GuiConfig) -> Result<(), Error> {
66    // Server and GUI are communicating throug channels
67    let (action_s, action_r) = bounded(1000);
68    let (feedback_s, feedback_r) = bounded(1000);
69
70    let feedback_s = FeedbackSender {
71        sender: feedback_s,
72        repaint_signal: None,
73    };
74
75    let mut server = Server::new(action_r, feedback_s);
76
77    thread::spawn(move || {
78        server.run();
79    });
80
81    // Create the GUI application
82    let app = ProvolaGuiApp::new(opt, action_s, feedback_r);
83    let native_options = eframe::NativeOptions::default();
84
85    eframe::run_native(Box::new(app), native_options)
86}
87
88#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Eq)]
89pub enum ActionConfig {
90    BuildTestInputOutput(Language, Source, TestDataIn, TestDataOut),
91    TestRunner(TestRunnerInfo, TestRunnerOpt),
92}
93
94#[derive(serde::Deserialize, serde::Serialize, Default, Clone, Debug, PartialEq, Eq)]
95pub struct GuiConfig {
96    pub watch_path: Option<PathBuf>,
97    pub watch: bool,
98    pub action: Option<ActionConfig>,
99}
100
101impl TryFrom<&GuiConfig> for Action {
102    type Error = Error;
103
104    fn try_from(opt: &GuiConfig) -> Result<Self, Error> {
105        let action_cfg = opt.action.as_ref().ok_or(Error::NothingToDo)?;
106
107        let action = match action_cfg {
108            ActionConfig::BuildTestInputOutput(lang, source, input, output) => {
109                Action::BuildTestInputOutput(*lang, source.clone(), input.clone(), output.clone())
110            }
111            ActionConfig::TestRunner(info, opt) => {
112                let test_runner = make_test_runner(info.clone());
113                Action::TestRunner(test_runner?, opt.clone())
114            }
115        };
116
117        Ok(action)
118    }
119}