Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c22a66c470 | ||
|
|
ae936420e0 | ||
|
|
1aab0cf4a5 |
@@ -265,6 +265,10 @@ It refreshes over the connections that are already open. `^r` logs in again,
|
||||
which is how a session that has died is recovered; doing that every ten seconds
|
||||
would be three logins a minute for nothing.
|
||||
|
||||
`^l` is not the only way in: making a machine from a template turns it on, since
|
||||
a clone that has just been started is exactly the screen this is for, and says
|
||||
so in the same line that reports the machine.
|
||||
|
||||
Two things come with it.
|
||||
|
||||
**The line under the table says what just changed.** A table shows what is; this
|
||||
@@ -479,10 +483,17 @@ puts it somewhere other than the template's own.
|
||||
**It does not wait.** A clone copies every disk the template has, which is
|
||||
minutes to the half hour, and a list frozen for that long is a list nobody would
|
||||
start one from. The task hangs off the template in vCenter, so the row it was
|
||||
started from shows how far along it is in its TASK column — `^l` makes that move
|
||||
on its own, and announces the new machine on the line under the table when it
|
||||
exists. `gvm new` on the command line does wait: a script that gets its prompt
|
||||
back wants the machine to be there.
|
||||
started from shows how far along it is in its TASK column. `gvm new` on the
|
||||
command line does wait: a script that gets its prompt back wants the machine to
|
||||
be there.
|
||||
|
||||
**And it goes to that column.** The machine's sheet — which is where the menu
|
||||
was opened and a page of facts about the *template* — is put away, the list
|
||||
comes back with the cursor still on the row the clone hangs off, and live mode
|
||||
turns itself on if it was off, so the percentage moves on its own and the line
|
||||
under the table announces the new machine when it exists. `^l` ends it again.
|
||||
Naming the key that would make an invisible figure move is three steps of
|
||||
housekeeping after the one decision that mattered.
|
||||
|
||||
The rest of a template's menu is greyed out with "a template" next to it, because
|
||||
vSphere will not start one, snapshot one or reconfigure one. They are greyed
|
||||
@@ -534,16 +545,23 @@ are open —
|
||||
|
||||
customise web05 how v308
|
||||
|
||||
▸ leave the guest as the template made it
|
||||
this site — fhi-berlin.mpg.de, gateway 10.0.0.1
|
||||
leave the guest as the template made it
|
||||
▸ this site — fhi-berlin.mpg.de, gateway 10.0.0.1
|
||||
linux-static
|
||||
windows-domain
|
||||
|
||||
— and then one line for the address, which says in words what leaving it empty
|
||||
would do:
|
||||
The cursor starts on gvm's own road wherever the site is set up: a domain, a
|
||||
netmask and a gateway written into `~/.gvmrc` are written down so that machines
|
||||
get them, and the answer wanted nearly every time should not be the one that has
|
||||
to be arrowed to. Where the site is not set up it starts at the top instead, on
|
||||
the answer that is never wrong. Nothing is decided by where it sits — the picker
|
||||
picks on Enter, and the confirmation still says in full what will happen.
|
||||
|
||||
address for web05, or empty for DHCP:
|
||||
address for web05, or empty to leave it to linux-static:
|
||||
Then one line for the address, which says in words what leaving it empty would
|
||||
do:
|
||||
|
||||
address for web05, empty for DHCP:
|
||||
address for web05, empty to leave it to linux-static:
|
||||
|
||||
Where that picker does **not** appear, the confirmation says why rather than
|
||||
leaving a step to look broken — neither road is open, because the vCenter holds
|
||||
@@ -560,9 +578,16 @@ when it will not say.
|
||||
gvm new --from ubuntu-tpl --name web05 --ip auto
|
||||
|
||||
Where the site has a tool that hands addresses out, `--ip auto` asks it instead
|
||||
of making you look one up first. In the list the same word does it:
|
||||
of making you look one up first. In the list the same word does it, and it is
|
||||
already standing in the line — a site with a tool hands addresses out from it,
|
||||
so that is the answer Enter gives:
|
||||
|
||||
address for web05, "auto" for one from dns, empty for DHCP:
|
||||
address for web05, "auto" for one from dns, empty for DHCP: auto
|
||||
|
||||
It is offered rather than made the meaning of the empty line, so every answer
|
||||
keeps the meaning it had: erased back to nothing it is DHCP again, or whatever
|
||||
the chosen specification says. And Enter commits nothing — an address fetched
|
||||
here is handed straight back when the deployment is not confirmed.
|
||||
|
||||
It shells out to `dns`, the Infoblox helper — found on the path by that name, or
|
||||
named as `dnstool` in `~/.gvmrc` where it lives somewhere else. Everything it
|
||||
@@ -586,7 +611,8 @@ answer short of the same answer.
|
||||
the line that is left on the screen afterwards — this is a number somebody
|
||||
writes down:
|
||||
|
||||
web05 is being made at 141.14.140.182 — ubuntu-tpl shows how far along it is
|
||||
web05 is being made at 141.14.140.182 — live on, ubuntu-tpl shows how far
|
||||
along it is
|
||||
|
||||
Three more things about it are deliberate:
|
||||
|
||||
|
||||
+93
-20
@@ -372,7 +372,19 @@ func (b *browser) openPicker(r vmRow, kind pickKind) {
|
||||
// callback runs after the picker has closed, so that what it does — a
|
||||
// confirmation, another question — has the screen to itself.
|
||||
func (b *browser) choose(title string, r vmRow, lines []string, chosen func(int)) {
|
||||
b.pick = &picker{title: title, row: r, lines: lines, chosen: chosen}
|
||||
b.chooseAt(title, r, lines, 0, chosen)
|
||||
}
|
||||
|
||||
// chooseAt is the same with the cursor starting somewhere other than the top,
|
||||
// for a list whose ordinary answer is not its first line. Where the cursor
|
||||
// starts is the only default a picker has — nothing is picked until Enter — so
|
||||
// it belongs on the line somebody nearly always wants, and the others are one
|
||||
// key away.
|
||||
func (b *browser) chooseAt(title string, r vmRow, lines []string, at int, chosen func(int)) {
|
||||
if at < 0 || at >= len(lines) {
|
||||
at = 0
|
||||
}
|
||||
b.pick = &picker{title: title, row: r, lines: lines, chosen: chosen, sel: at}
|
||||
}
|
||||
|
||||
func (b *browser) closePicker() { b.pick = nil }
|
||||
@@ -594,8 +606,14 @@ func sizePrompt(r vmRow, sz sizing, k sizeKind) string {
|
||||
// minutes to the half hour, and a list that is frozen for that long is a list
|
||||
// nobody will start one from. vCenter carries the task either way, and it hangs
|
||||
// off the template — so the row this was started from shows how far along it is
|
||||
// in its TASK column, and ^l makes that move on its own. The machine itself
|
||||
// turns up in the list when it exists, which live mode announces.
|
||||
// in its TASK column. The machine itself turns up in the list when it exists,
|
||||
// which live mode announces.
|
||||
//
|
||||
// Which is why the end of it is a screen and not only a sentence: the sheet
|
||||
// goes away, the list comes back with the cursor still on the template, and
|
||||
// live mode turns itself on. Telling somebody that a figure they cannot see
|
||||
// exists, and which key would make it move, is three steps of housekeeping
|
||||
// after the one decision that mattered.
|
||||
//
|
||||
// The question at the end is the plain one. Nothing is lost here; something is
|
||||
// made, and what has to be read before it is made is *where* — which is why
|
||||
@@ -671,21 +689,9 @@ func (b *browser) deploy(r vmRow) {
|
||||
return
|
||||
}
|
||||
|
||||
lines := []string{"leave the guest as the template made it"}
|
||||
kinds := []string{""} // what each line means: "" none, "-" gvm's own, else a name
|
||||
if own {
|
||||
// What it will do, not where the settings are kept: a picker line that
|
||||
// is mostly an absolute path says nothing about the choice being made,
|
||||
// and `gvm config` is where the file is named.
|
||||
lines = append(lines, SF("this site — %s, gateway %s", b.site.domain, b.site.gateway))
|
||||
kinds = append(kinds, "-")
|
||||
}
|
||||
for _, name := range specs {
|
||||
lines = append(lines, name)
|
||||
kinds = append(kinds, name)
|
||||
}
|
||||
lines, kinds, at := customChoices(b.site, specs, own)
|
||||
|
||||
b.choose(SF("customise %s how", name), r, lines, func(i int) {
|
||||
b.chooseAt(SF("customise %s how", name), r, lines, at, func(i int) {
|
||||
switch kinds[i] {
|
||||
case "":
|
||||
b.deployAsk(r, src, target, name, deployOpts{}, deployStep{})
|
||||
@@ -699,13 +705,50 @@ func (b *browser) deploy(r vmRow) {
|
||||
})
|
||||
}
|
||||
|
||||
// customChoices is what the customisation picker offers, what each line means,
|
||||
// and where its cursor starts.
|
||||
//
|
||||
// kinds is the meaning, carried beside the lines rather than worked out from an
|
||||
// index: "" leaves the guest alone, "-" is the specification gvm writes from
|
||||
// ~/.gvmrc, and anything else is the name of one the vCenter holds.
|
||||
//
|
||||
// The cursor starts on gvm's own road where the site is set up. A site whose
|
||||
// domain, netmask and gateway have been written down is a site whose machines
|
||||
// are meant to be given them — that is what writing them down was for — and the
|
||||
// answer wanted nearly every time should not be the one that has to be arrowed
|
||||
// to. Where there is no such road it starts at the top, on the answer that is
|
||||
// never wrong: leave the guest as the template made it.
|
||||
//
|
||||
// Nothing is decided by the cursor sitting there. The picker chooses on Enter,
|
||||
// the address is asked for after it, and the confirmation still says in full
|
||||
// what will happen.
|
||||
func customChoices(st site, specs []string, own bool) (lines, kinds []string, at int) {
|
||||
lines = []string{"leave the guest as the template made it"}
|
||||
kinds = []string{""}
|
||||
if own {
|
||||
// What it will do, not where the settings are kept: a picker line that
|
||||
// is mostly an absolute path says nothing about the choice being made,
|
||||
// and `gvm config` is where the file is named.
|
||||
lines = append(lines, SF("this site — %s, gateway %s", st.domain, st.gateway))
|
||||
kinds = append(kinds, "-")
|
||||
at = len(lines) - 1
|
||||
}
|
||||
for _, name := range specs {
|
||||
lines = append(lines, name)
|
||||
kinds = append(kinds, name)
|
||||
}
|
||||
return lines, kinds, at
|
||||
}
|
||||
|
||||
// deployAddress asks for the address, which is the other half of what a
|
||||
// specification cannot know: it holds the netmask, the gateway and the domain,
|
||||
// and the machine holds its own number.
|
||||
//
|
||||
// Empty is an answer: it leaves whatever the specification says, which is
|
||||
// usually DHCP, and that is the ordinary case on a network that hands out
|
||||
// addresses.
|
||||
// addresses. Where the site has a tool of its own, the ordinary case is the
|
||||
// other one — so "auto" is in the line when the question appears, and Enter
|
||||
// takes it.
|
||||
func (b *browser) deployAddress(r vmRow, src deploySource, t deployTarget, name string, opts deployOpts) {
|
||||
// Empty is an answer, and it means two different things: with gvm's own
|
||||
// specification it leaves the adapter on DHCP, and with one of the
|
||||
@@ -722,11 +765,24 @@ func (b *browser) deployAddress(r vmRow, src deploySource, t deployTarget, name
|
||||
leave = "empty to leave it to " + opts.spec
|
||||
}
|
||||
ask := SF("address for %s, %s: ", name, leave)
|
||||
|
||||
// And where there is a tool, its word is standing in the line already, so
|
||||
// Enter is the whole answer. That is what the tool is configured for: a
|
||||
// site that has one hands out addresses from it, and typing "auto" every
|
||||
// time is asking somebody to confirm the ordinary case by hand.
|
||||
//
|
||||
// Offered rather than made the meaning of the empty line, so that every
|
||||
// answer keeps the meaning it had: erased back to nothing it is DHCP again,
|
||||
// or whatever the specification says. Nor does pressing Enter commit
|
||||
// anything — an address fetched here is handed straight back where the
|
||||
// deployment is not confirmed (deployStep.giveBack).
|
||||
preset := ""
|
||||
if opts.dns.there() {
|
||||
ask = SF("address for %s, %q for one from dns, %s: ", name, autoIP, leave)
|
||||
preset = autoIP
|
||||
}
|
||||
|
||||
ip, ok := b.input(ask)
|
||||
ip, ok := b.inputWith(ask, preset)
|
||||
if !ok {
|
||||
b.setStatus(colDim, "nothing done")
|
||||
return
|
||||
@@ -795,13 +851,30 @@ func (b *browser) deployAsk(r vmRow, src deploySource, t deployTarget, name stri
|
||||
b.setStatus(colErr, err.Error())
|
||||
return
|
||||
}
|
||||
// And then the screen it can be watched on. This was started from the
|
||||
// template's sheet — a page of facts about the machine being copied, which
|
||||
// is not what anybody is here for now. The list is: the clone's progress
|
||||
// stands in the TASK column of the row the cursor is already on, the new
|
||||
// machine turns up there when it exists, and the line under the table says
|
||||
// so. Live mode goes with it, or the percentage would sit where it was
|
||||
// until somebody pressed a key.
|
||||
//
|
||||
// Decided before done, which is what puts the task on the row, and applied
|
||||
// after it, so that the interval it schedules is the busy one.
|
||||
watching := ""
|
||||
if !b.live {
|
||||
watching = "live on, "
|
||||
}
|
||||
b.closeDetail()
|
||||
|
||||
// The address is in the line that survives, not only in the question that
|
||||
// was answered a moment ago: it is the one thing here somebody writes down.
|
||||
at := ""
|
||||
if opts.ip != "" && opts.customising() {
|
||||
at = " at " + opts.ip
|
||||
}
|
||||
b.done(SF("%s is being made%s — %s shows how far along it is", name, at, src.name))
|
||||
b.done(SF("%s is being made%s — %s%s shows how far along it is", name, at, watching, src.name))
|
||||
b.watchLive()
|
||||
}
|
||||
|
||||
// working puts a line on the screen before an operation that will block the loop
|
||||
|
||||
@@ -1012,13 +1012,7 @@ func (b *browser) detailKey(k key) {
|
||||
|
||||
switch k.special {
|
||||
case keyEsc, keyEnter, keyBackspace, keyLeft:
|
||||
b.detail, b.dscroll = nil, 0
|
||||
// The events go with the visit, not with the machine: coming back to a
|
||||
// sheet half an hour later and finding half-hour-old events under a
|
||||
// label that says nothing about when they were read would be the one
|
||||
// stale thing on an otherwise freshly read screen. They are one
|
||||
// keystroke away again.
|
||||
b.events, b.eventsOf = nil, ""
|
||||
b.closeDetail()
|
||||
b.setStatus("", "")
|
||||
case keyCtrlA:
|
||||
b.openMenu()
|
||||
@@ -1306,8 +1300,18 @@ func (e *editor) key(k key) (finished, accepted bool) {
|
||||
// input reads one line in the status area and reports whether it was finished
|
||||
// rather than abandoned. It has its own key loop, so nothing that is typed here
|
||||
// reaches the filter.
|
||||
func (b *browser) input(label string) (string, bool) {
|
||||
b.edit = &editor{label: label}
|
||||
func (b *browser) input(label string) (string, bool) { return b.inputWith(label, "") }
|
||||
|
||||
// inputWith is the same with an answer already standing in the line, for a
|
||||
// question that has one ordinary answer worth offering: Enter takes it, and
|
||||
// Backspace is how it is refused. It is a default one can see — which is the
|
||||
// only kind worth having, since a question whose answer is decided by what was
|
||||
// not typed is one nobody knows they have agreed to.
|
||||
//
|
||||
// The cursor sits at the end of it, so typing carries on from the answer rather
|
||||
// than into the middle of it.
|
||||
func (b *browser) inputWith(label, preset string) (string, bool) {
|
||||
b.edit = &editor{label: label, runes: []rune(preset), cursor: len([]rune(preset))}
|
||||
defer func() { b.edit = nil }()
|
||||
|
||||
for {
|
||||
@@ -1717,6 +1721,19 @@ func (b *browser) openDetail() {
|
||||
b.dscroll = 0
|
||||
}
|
||||
|
||||
// closeDetail puts the sheet away and leaves the list underneath it. It is what
|
||||
// Esc does, and also what an action does when what happens next is to be
|
||||
// watched in the table rather than read on one machine's page (actions.go).
|
||||
//
|
||||
// The events go with the visit, not with the machine: coming back to a sheet
|
||||
// half an hour later and finding half-hour-old events under a label that says
|
||||
// nothing about when they were read would be the one stale thing on an
|
||||
// otherwise freshly read screen. They are one keystroke away again.
|
||||
func (b *browser) closeDetail() {
|
||||
b.detail, b.dscroll = nil, 0
|
||||
b.events, b.eventsOf = nil, ""
|
||||
}
|
||||
|
||||
// eventSheet is the history as sheet lines: the label on the first, each line in
|
||||
// the colour of its own severity. It is appended to the sheet rather than built
|
||||
// into vmDetail because vmDetail asks nothing of the network and this is the one
|
||||
|
||||
@@ -380,6 +380,29 @@ func TestTheSheetClosesWhenItsMachineGoes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Closing the sheet leaves nothing of the visit behind. The events are the part
|
||||
// that would keep: they are read once, on request, and a sheet opened again
|
||||
// half an hour later must not show them under a label that says nothing about
|
||||
// when they were read. Esc does this, and so does a deployment, which puts the
|
||||
// sheet away to leave the list watching the clone (actions.go).
|
||||
func TestClosingTheSheetForgetsTheVisit(t *testing.T) {
|
||||
b := testBrowser("ubuntu-tpl", "web01")
|
||||
b.applySort()
|
||||
b.events = []eventLine{{text: "08.09. 11:41 Cannot connect to host esx03", col: colFull}}
|
||||
b.eventsOf = b.current().id()
|
||||
b.openDetail()
|
||||
b.dscroll = 4
|
||||
|
||||
b.closeDetail()
|
||||
|
||||
if b.detail != nil || b.dscroll != 0 {
|
||||
t.Errorf("the sheet is still there: %d lines, scrolled to %d", len(b.detail), b.dscroll)
|
||||
}
|
||||
if b.events != nil || b.eventsOf != "" {
|
||||
t.Errorf("the events of %q were kept: %v", b.eventsOf, b.events)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetailSheetHasTheParameters(t *testing.T) {
|
||||
sheet := sheetText(vmDetail(testRow("web01", true, "10.0.0.5"), []string{"none"}, colOff))
|
||||
|
||||
|
||||
@@ -571,6 +571,43 @@ func TestTheSiteComesOutOfTheConfiguration(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The picker opens on the road gvm writes itself where the site has been set
|
||||
// up: a domain, a netmask and a gateway in ~/.gvmrc are there so that machines
|
||||
// get them, and the answer wanted nearly every time must not be the one that
|
||||
// has to be arrowed to. Where there is no such road it opens at the top, on the
|
||||
// answer that is never wrong.
|
||||
func TestTheCustomisationPickerOpensOnTheSiteRoad(t *testing.T) {
|
||||
st := testSite()
|
||||
|
||||
lines, kinds, at := customChoices(st, []string{"linux-static", "windows"}, true)
|
||||
if len(lines) != 4 || len(kinds) != len(lines) {
|
||||
t.Fatalf("the picker offers %d lines and %d meanings: %v", len(lines), len(kinds), lines)
|
||||
}
|
||||
if kinds[at] != "-" {
|
||||
t.Errorf("it opens on %q, which is %q — want gvm's own road", lines[at], kinds[at])
|
||||
}
|
||||
// And that line says what it will do, in the site's own words: a default
|
||||
// nobody can read is one nobody knows they are taking.
|
||||
if !strings.Contains(lines[at], st.domain) || !strings.Contains(lines[at], st.gateway) {
|
||||
t.Errorf("the line it opens on is %q, want the domain and the gateway in it", lines[at])
|
||||
}
|
||||
// The vCenter's own are still there, after it and in order.
|
||||
if lines[2] != "linux-static" || lines[3] != "windows" {
|
||||
t.Errorf("the vCenter's specifications came out as %v", lines[2:])
|
||||
}
|
||||
|
||||
// No site, and the cursor is at the top rather than on somebody else's
|
||||
// specification: leaving the guest as the template made it is the one
|
||||
// answer that cannot be wrong.
|
||||
lines, kinds, at = customChoices(site{}, []string{"linux-static"}, false)
|
||||
if at != 0 || kinds[at] != "" {
|
||||
t.Errorf("with no site it opens on %q (%q)", lines[at], kinds[at])
|
||||
}
|
||||
if len(lines) != 2 {
|
||||
t.Errorf("with no site the picker offers %v", lines)
|
||||
}
|
||||
}
|
||||
|
||||
// The question that asks for an address has to read as a sentence on both
|
||||
// roads. It did not: the interactive half settles which road before it asks,
|
||||
// so at that moment every field is still empty — which read as "no
|
||||
@@ -581,6 +618,8 @@ func TestTheAddressQuestionReadsAsASentence(t *testing.T) {
|
||||
t.Setenv("COLUMNS", "100")
|
||||
t.Setenv("LINES", "24")
|
||||
|
||||
tool, _ := fakeInfoblox(t) // for its there(), not for its answers
|
||||
|
||||
for _, c := range []struct {
|
||||
what string
|
||||
opts deployOpts
|
||||
@@ -589,6 +628,12 @@ func TestTheAddressQuestionReadsAsASentence(t *testing.T) {
|
||||
{"gvm's own specification", deployOpts{how: customSite, st: testSite()}, "empty for DHCP"},
|
||||
{"one the vCenter holds", deployOpts{how: customSpec, spec: "linux-static"},
|
||||
"leave it to linux-static"},
|
||||
// With an address tool, the answer it would give is standing in the
|
||||
// line already: that is what the tool is there for, and the question
|
||||
// still has to say what the other two answers mean.
|
||||
{"a site with an address tool",
|
||||
deployOpts{how: customSite, st: testSite(), dns: tool},
|
||||
`"auto" for one from dns, empty for DHCP: auto`},
|
||||
} {
|
||||
r := templateRow("ubuntu-tpl")
|
||||
b := &browser{rows: []vmRow{r}, view: []int{0}}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
// Everything else in the interactive half happens because somebody pressed a
|
||||
// key. This is the part that happens because time passed: `^l` turns it on and
|
||||
// the list re-reads itself every few seconds, which turns gvm from something one
|
||||
// looks at into something one leaves open on a second screen.
|
||||
// looks at into something one leaves open on a second screen. Starting a clone
|
||||
// turns it on as well (watchLive), because that is a screen made to be watched
|
||||
// whether or not anybody thought to ask for it first.
|
||||
//
|
||||
// Three things come with it, and they are here rather than in browse.go because
|
||||
// they only mean anything together:
|
||||
@@ -69,6 +71,27 @@ func (b *browser) toggleLive() {
|
||||
b.setStatus(colBusy, "live on — the list re-reads itself; ^l off")
|
||||
}
|
||||
|
||||
// watchLive turns live mode on because something with a figure to watch was
|
||||
// just set going, and reports whether it had to. ^l is somebody asking for a
|
||||
// screen that keeps itself up to date; this is gvm deciding that a clone it has
|
||||
// just started makes the list one of those, since the progress in the TASK
|
||||
// column is the whole reason to still be looking at it. It stays on afterwards,
|
||||
// like the mode it is — ^l ends it, and the title says it is running.
|
||||
//
|
||||
// Unlike toggleLive it does not sweep at once. The caller has just re-read the
|
||||
// row it acted on, so the first tick belongs one interval away rather than now
|
||||
// — and that interval is already the busy one, because that row is the one
|
||||
// carrying the task.
|
||||
func (b *browser) watchLive() bool {
|
||||
if b.live {
|
||||
return false
|
||||
}
|
||||
b.live = true
|
||||
b.liveGap = b.liveInterval()
|
||||
b.liveNext = time.Now().Add(b.liveGap)
|
||||
return true
|
||||
}
|
||||
|
||||
// liveReady reports whether a tick may happen now. Only the list and a
|
||||
// machine's sheet are refreshed underneath somebody: the menu decides what it
|
||||
// offers from the state it was drawn with, the picker holds a list of snapshots
|
||||
|
||||
@@ -485,3 +485,34 @@ func TestToggleLiveSaysSoAndLooksNow(t *testing.T) {
|
||||
t.Errorf("turning it off said %q", b.status)
|
||||
}
|
||||
}
|
||||
|
||||
// A clone that has just been started makes the list a screen worth watching, so
|
||||
// live mode turns itself on rather than leaving a percentage that moves only
|
||||
// when somebody remembers ^l. The first tick is an interval away rather than
|
||||
// now — the action re-read its own row a moment ago — and that interval is the
|
||||
// busy one, because the row it re-read is the one carrying the task.
|
||||
func TestStartingAClonePutsTheListOnWatch(t *testing.T) {
|
||||
b := testBrowser("ubuntu-tpl", "web01")
|
||||
b.rows[0].task = &runningTask{what: "clone", progress: 2}
|
||||
|
||||
if !b.watchLive() {
|
||||
t.Fatal("watchLive did not report that it had to turn live mode on")
|
||||
}
|
||||
if !b.live {
|
||||
t.Fatal("live mode is off after a clone was started")
|
||||
}
|
||||
if d := b.liveIn(); d > liveBusy || d < liveBusy/2 {
|
||||
t.Errorf("the first refresh is %s away, want about %s", d, liveBusy)
|
||||
}
|
||||
|
||||
// Already on is not a reason to touch it: a tick that was due in half a
|
||||
// second must not be pushed back to two because a second clone was started.
|
||||
b.liveNext = time.Now().Add(liveBusy / 4)
|
||||
due := b.liveNext
|
||||
if b.watchLive() {
|
||||
t.Error("watchLive reported turning on a mode that was already on")
|
||||
}
|
||||
if !b.liveNext.Equal(due) {
|
||||
t.Errorf("the pending tick moved from %s to %s", due, b.liveNext)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.3.7
|
||||
1.3.10
|
||||
|
||||
Reference in New Issue
Block a user