progress: add test for rust progress rendering

Summary:
Add roundtrip test-progress-rust-renderer.t to cover the rust:simple renderer and rust's progress rendering loop.

The python progress tests wrap the python renderer to synchronize output and simplify tests. To achieve something similar for rust, I added a synchronization point to the progress registry, and a "lockstep" config flag that lets tests manually advance the progress rendering loop. I considered multiple other approaches, but I like this one because it exercises the real progress rendering code, gets rid of sleeps during tests, and was a pretty simple change.

Reviewed By: quark-zju

Differential Revision: D32025570

fbshipit-source-id: df41f8efeea2e9ac00f6ffa1c84f390066fd3959
This commit is contained in:
Muir Manders 2021-11-02 14:32:21 -07:00 committed by Facebook GitHub Bot
parent d3da261c32
commit 1e56a67d15
9 changed files with 183 additions and 11 deletions

View File

@ -29,9 +29,9 @@ pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
m.add(py, "model", model_mod)?;
let render_mod = PyModule::new(py, &format!("{}.render", name))?;
use render::debug;
use render::simple;
use render::*;
render_mod.add(py, "simple", py_fn!(py, simple()))?;
render_mod.add(py, "step", py_fn!(py, step()))?;
render_mod.add(py, "debug", py_fn!(py, debug()))?;
m.add(py, "render", render_mod)?;

View File

@ -8,6 +8,7 @@
//! This module exposes Rust's progress rendering to Python.
use cpython::*;
use cpython_ext::PyNone;
use progress_model::Registry;
pub(crate) fn simple(_py: Python) -> PyResult<String> {
@ -22,3 +23,8 @@ pub(crate) fn debug(_py: Python) -> PyResult<String> {
let reg = Registry::main();
Ok(format!("{:?}", reg))
}
pub(crate) fn step(_py: Python) -> PyResult<PyNone> {
Registry::main().step();
Ok(PyNone)
}

View File

@ -322,6 +322,9 @@ fn spawn_progress_thread(
let interval = Duration::from_secs_f64(config.get_or("progress", "refresh", || 0.1)?)
.max(Duration::from_millis(50));
// lockstep is used by tests to control progress rendering run loop.
let lockstep = config.get_or("progress", "lockstep", || false)?;
// Limit how often we write runlog. This config knob is primarily for tests to lower.
let runlog_interval =
Duration::from_secs_f64(config.get_or("runlog", "progress_refresh", || 0.5)?).max(interval);
@ -348,6 +351,10 @@ fn spawn_progress_thread(
while Weak::upgrade(&in_scope).is_some() {
let now = Instant::now();
if lockstep {
registry.wait();
}
if !disable_rendering {
let mut text = (render_function)(&registry, &config);
if text != last_text {
@ -377,7 +384,10 @@ fn spawn_progress_thread(
}
registry.remove_orphan_progress_bar();
thread::sleep(interval);
if !lockstep {
thread::sleep(interval);
}
}
});

View File

@ -8,6 +8,8 @@
use std::sync::Arc;
use once_cell::sync::Lazy;
use parking_lot::Condvar;
use parking_lot::Mutex;
use parking_lot::RwLock;
use parking_lot::RwLockUpgradableReadGuard;
@ -23,6 +25,7 @@ use crate::ProgressBar;
/// (ex. "fetching files 123/456")
#[derive(Default, Clone, Debug)]
pub struct Registry {
render_cond: Arc<(Mutex<bool>, Condvar)>,
inner: Arc<RwLock<Inner>>,
}
@ -95,10 +98,41 @@ impl Registry {
pub fn main() -> &'static Self {
static REGISTRY: Lazy<Registry> = Lazy::new(|| {
tracing::debug!("main progress Registry initialized");
Default::default()
Registry {
render_cond: Arc::new((Mutex::new(false), Condvar::new())),
..Default::default()
}
});
&REGISTRY
}
/// step/wait provide a mechanism for tests to step through
/// rendering/handling of the registry in a controlled manner. The
/// test calls step() which unblocks the wait()er. Then step()
/// waits for the next wait() call, ensuring that the registry
/// processing loop finished its iteration.
pub fn step(&self) {
let &(ref lock, ref var) = &*self.render_cond;
let mut ready = lock.lock();
*ready = true;
var.notify_one();
// Wait for wait() to notify us that it completed an iteration.
var.wait(&mut ready);
}
/// See step().
pub fn wait(&self) {
let &(ref lock, ref var) = &*self.render_cond;
let mut ready = lock.lock();
if *ready {
// We've come around to the next iteration's wait() call -
// notify step() that we finished an iteration.
*ready = false;
var.notify_one();
}
// Wait for next step() call.
var.wait(&mut ready);
}
}
#[cfg(test)]

View File

@ -2,9 +2,9 @@ from __future__ import absolute_import
import time
import bindings
from edenscm.mercurial import progress, pycompat, registrar, util
cmdtable = {}
command = registrar.command(cmdtable)
@ -59,7 +59,7 @@ def progresstest(ui, loops, total, **opts):
prog.value = (i, unicodeloopitems[i % len(unicodeloopitems)])
else:
prog.value = (i, "loop %s" % i)
progress.getengine().pump(_faketime.increment())
syncrender()
if nested:
nestedtotal = 5 if i % 6 == 5 else 2
with progress.bar(
@ -67,7 +67,7 @@ def progresstest(ui, loops, total, **opts):
) as nestedprog:
for j in range(nestedtotal + 1):
nestedprog.value = (j, "nest %s" % j)
progress.getengine().pump(_faketime.increment())
syncrender()
@command("bytesprogresstest", norepo=True)
@ -92,7 +92,7 @@ def bytesprogresstest(ui):
) as prog:
for value in values:
prog.value = (value, "%s bytes" % value)
progress.getengine().pump(_faketime.increment())
syncrender()
def uisetup(ui):
@ -109,3 +109,8 @@ def uisetup(ui):
self._show(now)
progress.getengine().__class__ = syncengine
def syncrender():
progress.getengine().pump(_faketime.increment())
bindings.progress.render.step()

View File

@ -2,7 +2,7 @@
$ enable progress
$ setconfig extensions.progresstest="$TESTDIR/progresstest.py"
$ setconfig progress.delay=0 progress.changedelay=2 progress.refresh=1 progress.assume-tty=true
$ setconfig progress.delay=0 progress.changedelay=2 progress.refresh=1 progress.assume-tty=true progress.lockstep=True
simple test
$ hg progresstest 4 4

View File

@ -3,7 +3,7 @@
$ enable progress color
$ setconfig extensions.progresstest="$TESTDIR/progresstest.py"
$ setconfig progress.delay=0 progress.changedelay=2 progress.refresh=1 progress.assume-tty=true
$ setconfig progress.renderer=fancy progress.width=60 ui.color=debug
$ setconfig progress.renderer=fancy progress.width=60 ui.color=debug progress.lockstep=True
simple test
$ hg progresstest 4 4

View File

@ -0,0 +1,117 @@
#chg-compatible
$ enable progress
$ setconfig extensions.progresstest="$TESTDIR/progresstest.py"
$ setconfig progress.delay=0 progress.assume-tty=true progress.lockstep=True progress.renderer=rust:simple
simple test
$ hg progresstest 4 4
Progress [> ] 0/4 cycles loop 0\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Progress [===> ] 1/4 cycles loop 1\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Progress [=======> ] 2/4 cycles loop 2\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Progress [===========> ] 3/4 cycles loop 3\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Progress [===============] 4/4 cycles loop 4\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J (no-eol) (esc)
test nested topics
$ hg progresstest --nested 2 2
Progress [> ] 0/2 cycles loop 0\r (no-eol) (esc)
\r (no-eol) (esc)
 Progress [> ] 0/2 cycles loop 0
Nested [> ] 0/2 nest 0\r (no-eol) (esc)
\x1b[1A\r (no-eol) (esc)
 Progress [> ] 0/2 cycles loop 0
Nested [=======> ] 1/2 nest 1\r (no-eol) (esc)
\x1b[1A\r (no-eol) (esc)
 Progress [> ] 0/2 cycles loop 0
Nested [===============] 2/2 nest 2\r (no-eol) (esc)
\x1b[1A\r (no-eol) (esc)
 Progress [=======> ] 1/2 cycles loop 1
Nested [===============] 2/2 nest 2\r (no-eol) (esc)
\x1b[1A\r (no-eol) (esc)
 Progress [=======> ] 1/2 cycles loop 1
Nested [> ] 0/2 nest 0\r (no-eol) (esc)
\x1b[1A\r (no-eol) (esc)
 Progress [=======> ] 1/2 cycles loop 1
Nested [=======> ] 1/2 nest 1\r (no-eol) (esc)
\x1b[1A\r (no-eol) (esc)
 Progress [=======> ] 1/2 cycles loop 1
Nested [===============] 2/2 nest 2\r (no-eol) (esc)
\x1b[1A\r (no-eol) (esc)
 Progress [===============] 2/2 cycles loop 2
Nested [===============] 2/2 nest 2\r (no-eol) (esc)
\x1b[1A\r (no-eol) (esc)
 Progress [===============] 2/2 cycles loop 2
Nested [> ] 0/2 nest 0\r (no-eol) (esc)
\x1b[1A\r (no-eol) (esc)
 Progress [===============] 2/2 cycles loop 2
Nested [=======> ] 1/2 nest 1\r (no-eol) (esc)
\x1b[1A\r (no-eol) (esc)
 Progress [===============] 2/2 cycles loop 2
Nested [===============] 2/2 nest 2\r (no-eol) (esc)
\x1b[1A\r (no-eol) (esc)
\x1b[J (no-eol) (esc)
test count over total
$ hg progresstest 4 2
Progress [> ] 0/2 cycles loop 0\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Progress [=======> ] 1/2 cycles loop 1\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Progress [===============] 2/2 cycles loop 2\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Progress [<=> ] 3/2 cycles loop 3\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Progress [<=> ] 4/2 cycles loop 4\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J (no-eol) (esc)
test rendering with bytes
$ hg bytesprogresstest
Bytes [> ] 0B/1111MB 0 bytes\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Bytes [> ] 10B/1111MB 10 bytes\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Bytes [> ] 250B/1111MB 250 bytes\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Bytes [> ] 999B/1111MB 999 bytes\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Bytes [> ] 1000B/1111MB 1000 bytes\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Bytes [> ] 1024B/1111MB 1024 bytes\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Bytes [> ] 22KB/1111MB 22000 bytes\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Bytes [> ] 1048KB/1111MB 1048576 bytes\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Bytes [> ] 1474KB/1111MB 1474560 bytes\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Bytes [=> ] 123MB/1111MB 123456789 bytes\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Bytes [=======> ] 555MB/1111MB 555555555 bytes\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Bytes [=============> ] 1000MB/1111MB 1000000000 bytes\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J Bytes [===============] 1111MB/1111MB 1111111111 bytes\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J (no-eol) (esc)
test unicode topic
$ hg --encoding utf-8 progresstest 4 4 --unicode
あいうえ [> ] 0/4 cycles あい\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J あいうえ [===> ] 1/4 cycles あいう\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J あいうえ [=======> ] 2/4 cycles あいうえ\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J あいうえ [===========> ] 3/4 cycles あい\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J あいうえ [===============] 4/4 cycles あいう\r (no-eol) (esc)
\r (no-eol) (esc)
\x1b[J (no-eol) (esc)

View File

@ -4,7 +4,7 @@
$ setconfig extensions.progresstest="$TESTDIR/progresstest.py"
$ setconfig progress.delay=0 progress.changedelay=2 progress.refresh=1 progress.assume-tty=true
$ setconfig progress.statefile="$TESTTMP/progressstate" progress.statefileappend=true
$ setconfig progress.fakedpid=42
$ setconfig progress.fakedpid=42 progress.lockstep=True
$ withprogress() {
> "$@"