fix(autocall): the entity's verdict is not the station's
Six faults, all found on the air this evening and all in the same
feature. The decision trace added here is what found the first one:
one line per period, saying what was on the air and why each station
was refused.
- "worked" was read from the ENTITY's status, which means the country
is in the log on this band and mode. On 10 m, where most countries
are, twenty decodes out of twenty-one were refused as worked — the
watched DXpedition among them. The entity decides what is NEEDED;
the callsign's own slot decides whether calling it is a duplicate.
candidateOf() is split out and tested because one line was wrong for
weeks and nothing could catch it.
- A multi-answer line was read only up to its first message. MSHV
answers two stations in one transmission ("YV5ALI RR73; F4BPO
<HK0/PY8WW> -08") and the second half was a report to us: the engine
saw a station working somebody else and dropped the target at the
moment the DX was answering. The decodes panel already read every
segment.
- The slot after a QSO belongs to our own 73. Handing straight on to
the next station took it, switching the DX call mid-sequence, and
the frame that closes the contact never went out whole.
- A station CALLING US is answered whether or not the log wants
anything from it. It was refused for having nothing to gain, so a
QSO would end, two stations would call, and both were ignored.
- Callability was tested when a station was CHOSEN and never again
while it was held: one picked on its CQ that then answered another
caller went on being called for the whole seven attempts.
- A watched callsign now outranks every station that is not on the
list. Lifted one rung at a time it sat at the bottom with nothing
needed from it, and was never reached on a busy band — the opposite
of what putting it on the list means.
Halt is now a verdict: the station is set aside for the session rather
than released, and the chase list does not override it. The attempts
cap lets the over finish instead of cutting the call that counted it,
and a rest is a rest. Only what the decodes list is SHOWING can be
called — the panel publishes the callsigns it shows, so there is one
definition of "shown" and not two.
This commit is contained in:
@@ -2,6 +2,7 @@ package autocall
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -56,13 +57,14 @@ func on() *Engine { return New(Settings{Enabled: true}) }
|
||||
// ── The ladder ────────────────────────────────────────────────────────────
|
||||
|
||||
func TestLadderOrder(t *testing.T) {
|
||||
// Every rung, in the order the operator asked for.
|
||||
// Every rung. The watched ones come FIRST, ordered among themselves by what
|
||||
// is needed — the list is the operator's answer, not a tie-breaker.
|
||||
order := []Candidate{
|
||||
cq("A", NeedDXCC, 0, watched), cq("B", NeedDXCC, 0),
|
||||
cq("C", NeedBand, 0, watched), cq("D", NeedBand, 0),
|
||||
cq("E", NeedMode, 0, watched), cq("F", NeedMode, 0),
|
||||
cq("G", NeedSlot, 0, watched), cq("H", NeedSlot, 0),
|
||||
cq("A", NeedDXCC, 0, watched), cq("C", NeedBand, 0, watched),
|
||||
cq("E", NeedMode, 0, watched), cq("G", NeedSlot, 0, watched),
|
||||
cq("I", NeedNone, 0, watched),
|
||||
cq("B", NeedDXCC, 0), cq("D", NeedBand, 0),
|
||||
cq("F", NeedMode, 0), cq("H", NeedSlot, 0),
|
||||
}
|
||||
for i := 1; i < len(order); i++ {
|
||||
if rank(order[i-1]) <= rank(order[i]) {
|
||||
@@ -76,10 +78,17 @@ func TestLadderOrder(t *testing.T) {
|
||||
}
|
||||
// And the pick agrees with the ladder, whatever order the period lists them.
|
||||
e := on()
|
||||
a := e.OnPeriod(period(0, order[7], order[3], order[0], order[5]))
|
||||
a := e.OnPeriod(period(0, order[8], order[5], order[0], order[6]))
|
||||
if a.Kind != DoReply || a.Decode.Call != "A" {
|
||||
t.Fatalf("picked %+v, want the watched new entity", a)
|
||||
}
|
||||
// A watched station with NOTHING needed still beats a new entity that is not
|
||||
// watched — the case that sent an operator hunting: a watched DXpedition sat
|
||||
// on the band all evening while the engine worked what the log wanted.
|
||||
e = on()
|
||||
if a := e.OnPeriod(period(2, cq("RARE", NeedDXCC, 0), cq("WATCHED", NeedNone, -20, watched))); a.Decode.Call != "WATCHED" {
|
||||
t.Errorf("picked %q, want the watched callsign", a.Decode.Call)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStrongestWinsBetweenEquals(t *testing.T) {
|
||||
@@ -232,24 +241,52 @@ func TestAReleasedStationYieldsToAnythingBetter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAReleasedStationIsCalledAgainWhenNothingBetterIsOnTheAir(t *testing.T) {
|
||||
func TestAReleasedStationRestsBeforeItIsCalledAgain(t *testing.T) {
|
||||
e := on()
|
||||
e.OnPeriod(period(0, cq("DX", NeedBand, -5)))
|
||||
tx := TXState{Transmitting: true, Msg: "DX " + me + " JN36"}
|
||||
for i := 0; i < 7; i++ {
|
||||
e.NoteTX(tx)
|
||||
}
|
||||
a := e.OnPeriod(period(2, cq("DX", NeedBand, -5)))
|
||||
if a.Kind != DoReply || a.Decode.Call != "DX" {
|
||||
t.Errorf("nothing better on the air and the station was not called again: %+v", a)
|
||||
// Still the only thing on the air, and still resting: seven calls, a halt,
|
||||
// and the same station called again four seconds later is not a rest.
|
||||
for _, n := range []int{2, 4, 6} { // all inside the two minutes
|
||||
if a := e.OnPeriod(period(n, cq("DX", NeedBand, -5))); a.Kind != DoNothing {
|
||||
t.Fatalf("period %d: called again during the rest: %+v", n, a)
|
||||
}
|
||||
}
|
||||
// Two minutes later (period 8 × 15 s = 2 min past the give-up) it may go
|
||||
// again — the station is still there and nothing better is.
|
||||
if a := e.OnPeriod(period(9, cq("DX", NeedBand, -5))); a.Kind != DoReply {
|
||||
t.Errorf("after the rest: %+v, want the station called again", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheAttemptsCapLetsTheOverFinish(t *testing.T) {
|
||||
e := on()
|
||||
e.OnPeriod(period(0, cq("DX", NeedDXCC, -5)))
|
||||
tx := TXState{Transmitting: true, Msg: "DX " + me + " JN36"}
|
||||
var a Action
|
||||
for i := 0; i < 7; i++ {
|
||||
a = e.NoteTX(tx)
|
||||
}
|
||||
if a.Kind != DoHalt {
|
||||
t.Fatalf("no halt after seven calls: %+v", a)
|
||||
}
|
||||
// The cap trips WHILE the seventh call is going out — cutting the carrier
|
||||
// there sends half a call. It asks the decoder to finish the over first.
|
||||
if !a.Soft {
|
||||
t.Error("the attempts cap cut the transmission that counted it")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAStationIsParkedAfterItsRounds(t *testing.T) {
|
||||
e := on()
|
||||
tx := TXState{Transmitting: true, Msg: "DX " + me + " JN36"}
|
||||
// Rounds are spaced past the rest (2 min = 8 periods) or the station would
|
||||
// simply be resting rather than parked.
|
||||
for round := 1; round <= 3; round++ {
|
||||
a := e.OnPeriod(period(round*2, cq("DX", NeedBand, -5)))
|
||||
a := e.OnPeriod(period(round*10, cq("DX", NeedBand, -5)))
|
||||
if a.Kind != DoReply {
|
||||
t.Fatalf("round %d: not called (%+v)", round, a)
|
||||
}
|
||||
@@ -259,7 +296,7 @@ func TestAStationIsParkedAfterItsRounds(t *testing.T) {
|
||||
}
|
||||
// Three series of seven is twenty-one calls. That is the end of it for this
|
||||
// session — the whole point of the exercise is that it cannot reach fifty.
|
||||
if a := e.OnPeriod(period(20, cq("DX", NeedBand, -5))); a.Kind != DoNothing {
|
||||
if a := e.OnPeriod(period(60, cq("DX", NeedBand, -5))); a.Kind != DoNothing {
|
||||
t.Errorf("a fourth series was started: %+v", a)
|
||||
}
|
||||
}
|
||||
@@ -271,9 +308,13 @@ func TestFinishedQSOMovesToTheNextPriority(t *testing.T) {
|
||||
e.OnPeriod(period(0, cq("DX", NeedDXCC, -5)))
|
||||
done := callsMe("DX", NeedDXCC, -5)
|
||||
done.Msg = me + " DX RR73"
|
||||
a := e.OnPeriod(period(2, done, cq("NEXT", NeedBand, -10)))
|
||||
// The period the QSO ends in belongs to our own 73 — nothing else is called.
|
||||
if a := e.OnPeriod(period(2, done, cq("NEXT", NeedBand, -10))); a.Kind != DoNothing {
|
||||
t.Fatalf("took the slot our 73 goes out in: %+v", a)
|
||||
}
|
||||
a := e.OnPeriod(period(4, cq("NEXT", NeedBand, -10)))
|
||||
if a.Kind != DoReply || a.Decode.Call != "NEXT" {
|
||||
t.Errorf("after the QSO ended: %+v, want the next priority in the same period", a)
|
||||
t.Errorf("next period: %+v, want the next priority", a)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,12 +323,14 @@ func TestFinishedQSOWithNoPriorityAnswersWhoeverIsCallingUs(t *testing.T) {
|
||||
e.OnPeriod(period(0, cq("DX", NeedDXCC, -5)))
|
||||
done := callsMe("DX", NeedDXCC, -5)
|
||||
done.Msg = me + " DX RR73"
|
||||
// Two stations calling us, nothing needed from either: the strongest wins.
|
||||
// Two stations calling us, nothing needed from either: the strongest wins —
|
||||
// on the period after the one our 73 goes out in.
|
||||
weak := callsMe("WEAK", NeedNone, -18)
|
||||
weak.Watched = true
|
||||
loud := callsMe("LOUD", NeedNone, -4)
|
||||
loud.Watched = true
|
||||
a := e.OnPeriod(period(2, done, weak, loud))
|
||||
e.OnPeriod(period(2, done))
|
||||
a := e.OnPeriod(period(4, weak, loud))
|
||||
if a.Kind != DoReply || a.Decode.Call != "LOUD" {
|
||||
t.Errorf("answered %+v, want the strongest of the stations calling us", a)
|
||||
}
|
||||
@@ -403,7 +446,8 @@ func TestChaseListTakesSeveralCallsigns(t *testing.T) {
|
||||
// single request.
|
||||
done := callsMe("VP6D", NeedDXCC, -22)
|
||||
done.Msg = me + " VP6D RR73"
|
||||
a = e.OnPeriod(period(4, done, cq("3Y0J", NeedSlot, -1)))
|
||||
e.OnPeriod(period(4, done, cq("3Y0J", NeedSlot, -1)))
|
||||
a = e.OnPeriod(period(6, cq("3Y0J", NeedSlot, -1)))
|
||||
if a.Kind != DoReply || a.Decode.Call != "3Y0J" {
|
||||
t.Errorf("after working VP6D: %+v, want the other station on the list", a)
|
||||
}
|
||||
@@ -460,3 +504,294 @@ func TestTheOtherReceiverCannotBreakTheQSOInProgress(t *testing.T) {
|
||||
t.Errorf("after the QSO ended, the other receiver was still locked out: %+v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// The one reported from the air: his RRR arrives, we are sending our 73, and
|
||||
// the engine picked the next station in the same instant. WSJT-X acts on a
|
||||
// reply at once — it dropped the exchange and started calling the new station,
|
||||
// cutting the 73 a second in.
|
||||
func TestNothingIsCalledOverOurOwnTransmission(t *testing.T) {
|
||||
e := on()
|
||||
e.OnPeriod(period(0, cq("DX", NeedDXCC, -5)))
|
||||
done := callsMe("DX", NeedDXCC, -5)
|
||||
done.Msg = me + " DX RRR"
|
||||
p := period(2, done, cq("NEXT", NeedBand, -10))
|
||||
p.TX = TXState{Transmitting: true, Msg: "DX " + me + " 73"}
|
||||
if a := e.OnPeriod(p); a.Kind != DoNothing {
|
||||
t.Fatalf("called %+v while our own over was still going out", a)
|
||||
}
|
||||
// The QSO is still released — only the next call waits.
|
||||
if e.Target() != "" {
|
||||
t.Errorf("target = %q after the QSO finished, want none", e.Target())
|
||||
}
|
||||
// Next period, carrier down: now it may take the next station.
|
||||
if a := e.OnPeriod(period(4, cq("NEXT", NeedBand, -10))); a.Kind != DoReply || a.Decode.Call != "NEXT" {
|
||||
t.Errorf("once the transmission ended: %+v, want the next station called", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheHoldClockRestartsWhenTheStationAnswers(t *testing.T) {
|
||||
e := on()
|
||||
e.OnPeriod(period(0, cq("DX", NeedDXCC, -5)))
|
||||
// Called for a long time — nearly the backstop — and then he answers.
|
||||
for p := 2; p <= 14; p += 2 {
|
||||
e.OnPeriod(period(p, cq("DX", NeedDXCC, -5)))
|
||||
}
|
||||
e.OnPeriod(period(16, callsMe("DX", NeedDXCC, -5)))
|
||||
// The exchange must not be halted by a backstop that was counting the wait.
|
||||
for _, p := range []int{18, 20} {
|
||||
if a := e.OnPeriod(period(p, callsMe("DX", NeedDXCC, -5))); a.Kind == DoHalt {
|
||||
t.Fatalf("period %d: halted an exchange in progress (%s)", p, a.Reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestARealNeedOutranksAnUnconfirmedOne(t *testing.T) {
|
||||
real := cq("REAL", NeedBand, -20)
|
||||
unconf := cq("UNCONF", NeedBand, -2)
|
||||
unconf.Unconfirmed = true
|
||||
// Same need, and the unconfirmed one is 18 dB louder. The band never worked
|
||||
// is still the catch: the other is a QSL to chase, not a QSO to make.
|
||||
e := on()
|
||||
if a := e.OnPeriod(period(0, unconf, real)); a.Decode.Call != "REAL" {
|
||||
t.Errorf("picked %q, want the band never worked", a.Decode.Call)
|
||||
}
|
||||
// Watched changes that, and is meant to: the list is the operator's answer.
|
||||
unconf.Watched = true
|
||||
e = on()
|
||||
if a := e.OnPeriod(period(0, unconf, real)); a.Decode.Call != "UNCONF" {
|
||||
t.Errorf("picked %q, want the watched station", a.Decode.Call)
|
||||
}
|
||||
unconf.Watched = false
|
||||
// Between two unwatched stations, an unconfirmed BAND still beats a real
|
||||
// SLOT: the band is the bigger prize whatever state it is in.
|
||||
slot := cq("SLOT", NeedSlot, 0)
|
||||
e = on()
|
||||
if a := e.OnPeriod(period(0, slot, unconf)); a.Decode.Call != "UNCONF" {
|
||||
t.Errorf("picked %q, want the unconfirmed band over a real slot", a.Decode.Call)
|
||||
}
|
||||
}
|
||||
|
||||
// ── The operator's Halt ───────────────────────────────────────────────────
|
||||
|
||||
func TestHaltSetsTheStationAsideForTheSession(t *testing.T) {
|
||||
e := on()
|
||||
if a := e.OnPeriod(period(0, cq("DX", NeedDXCC, -5))); a.Decode.Call != "DX" {
|
||||
t.Fatalf("not called: %+v", a)
|
||||
}
|
||||
// The operator stops it mid-call.
|
||||
if got := e.Halt(); got != "DX" {
|
||||
t.Fatalf("Halt returned %q, want the station being called", got)
|
||||
}
|
||||
if e.Target() != "" {
|
||||
t.Error("the target survived a halt")
|
||||
}
|
||||
// Still the loudest new entity on the air, period after period. It is the
|
||||
// operator's verdict, so it is not called again — not next period, not in
|
||||
// ten minutes.
|
||||
for _, n := range []int{2, 4, 40} {
|
||||
if a := e.OnPeriod(period(n, cq("DX", NeedDXCC, -5))); a.Kind != DoNothing {
|
||||
t.Errorf("period %d: called a station the operator had stopped: %+v", n, a)
|
||||
}
|
||||
}
|
||||
// Naming it explicitly does not override the operator either.
|
||||
e.SetSettings(Settings{Enabled: true, Only: "DX"})
|
||||
if a := e.OnPeriod(period(42, cq("DX", NeedDXCC, -5))); a.Kind != DoNothing {
|
||||
t.Errorf("the chase list overrode a halt: %+v", a)
|
||||
}
|
||||
// Switching auto-call off and on is what starts over.
|
||||
e.Reset()
|
||||
e.SetSettings(Settings{Enabled: true})
|
||||
if a := e.OnPeriod(period(44, cq("DX", NeedDXCC, -5))); a.Kind != DoReply {
|
||||
t.Errorf("after a restart the station is fair game again: %+v", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHaltWithNothingBeingCalledIsJustAHalt(t *testing.T) {
|
||||
e := on()
|
||||
if got := e.Halt(); got != "" {
|
||||
t.Errorf("Halt returned %q with no target", got)
|
||||
}
|
||||
if e.Greylisted() != 0 {
|
||||
t.Errorf("greylisted %d stations from an idle halt", e.Greylisted())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceSaysWhyNobodyWasCalled(t *testing.T) {
|
||||
var lines []string
|
||||
e := on()
|
||||
e.SetTrace(func(f string, args ...any) { lines = append(lines, fmt.Sprintf(f, args...)) })
|
||||
|
||||
worked := cq("A", NeedDXCC, -5, worked)
|
||||
nothing := cq("B", NeedNone, -5)
|
||||
busyOne := busy("C", NeedBand, -5)
|
||||
e.Halt() // nothing held: no-op, keeps the set empty
|
||||
e.OnPeriod(period(0, worked, nothing, busyOne))
|
||||
|
||||
if len(lines) == 0 {
|
||||
t.Fatal("tracing on and nothing was written")
|
||||
}
|
||||
got := lines[len(lines)-1]
|
||||
for _, want := range []string{"decodes=3", "callable=0", "worked=1", "nothing-needed=1", "busy=1"} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("trace %q does not say %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// And when it DOES call, the line names the candidates it ranked.
|
||||
lines = nil
|
||||
e.OnPeriod(period(2, cq("DX", NeedBand, -9, watched)))
|
||||
if len(lines) == 0 || !strings.Contains(lines[0], "DX(watched band,-9 dB,r114)") {
|
||||
t.Errorf("trace %v does not describe the station it called", lines)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAStationCallingUsIsAnsweredEvenWithNothingToGain(t *testing.T) {
|
||||
e := on()
|
||||
// A QSO has just ended and two stations are calling us. Neither is worth
|
||||
// anything to the log — and both are worth answering: they have heard us,
|
||||
// they are waiting, and the contact is one over away.
|
||||
weak := callsMe("WEAK", NeedNone, -18)
|
||||
loud := callsMe("LOUD", NeedNone, -3)
|
||||
a := e.OnPeriod(period(0, weak, loud))
|
||||
if a.Kind != DoReply || a.Decode.Call != "LOUD" {
|
||||
t.Fatalf("answered %+v, want the strongest of the stations calling us", a)
|
||||
}
|
||||
// A caller already worked on this band and mode is a duplicate, not a QSO.
|
||||
e = on()
|
||||
done := callsMe("DUPE", NeedNone, -1)
|
||||
done.Worked = true
|
||||
if a := e.OnPeriod(period(2, done)); a.Kind != DoNothing {
|
||||
t.Errorf("answered a station already in the log on this band and mode: %+v", a)
|
||||
}
|
||||
// And one the operator halted stays halted, however politely it calls.
|
||||
e = on()
|
||||
e.OnPeriod(period(4, cq("STOP", NeedDXCC, -5)))
|
||||
e.Halt()
|
||||
if a := e.OnPeriod(period(6, callsMe("STOP", NeedDXCC, -5))); a.Kind != DoNothing {
|
||||
t.Errorf("answered a station the operator had stopped: %+v", a)
|
||||
}
|
||||
// A needed station still comes first: a caller with nothing to gain is
|
||||
// answered when nothing better is on the air, which is what was asked for.
|
||||
e = on()
|
||||
if a := e.OnPeriod(period(8, callsMe("CALLER", NeedNone, 0), cq("RARE", NeedDXCC, -20))); a.Decode.Call != "RARE" {
|
||||
t.Errorf("picked %q, want the new entity ahead of a caller with nothing needed", a.Decode.Call)
|
||||
}
|
||||
}
|
||||
|
||||
func TestATargetThatStartsWorkingSomebodyElseIsDropped(t *testing.T) {
|
||||
e := on()
|
||||
// Picked on its CQ.
|
||||
if a := e.OnPeriod(period(0, cq("ON7GB", NeedSlot, +5))); a.Decode.Call != "ON7GB" {
|
||||
t.Fatalf("not called: %+v", a)
|
||||
}
|
||||
// Next period it is answering somebody else. Calling it is pointless: it is
|
||||
// committed, and the seven attempts would be spent transmitting at a
|
||||
// station that cannot hear them.
|
||||
busyNow := busy("ON7GB", NeedSlot, +5)
|
||||
busyNow.Msg = "PY2SAD ON7GB JO21"
|
||||
a := e.OnPeriod(period(2, busyNow))
|
||||
if a.Kind != DoHalt {
|
||||
t.Fatalf("kept calling a station in a QSO with someone else: %+v", a)
|
||||
}
|
||||
if !strings.Contains(a.Reason, "PY2SAD") {
|
||||
t.Errorf("reason %q does not name the station it is working", a.Reason)
|
||||
}
|
||||
if e.Target() != "" {
|
||||
t.Error("the target was not released")
|
||||
}
|
||||
// No verdict attached: the moment it CQs again it is fair game, with a full
|
||||
// allowance — it was never given up on.
|
||||
if a := e.OnPeriod(period(4, cq("ON7GB", NeedSlot, +5))); a.Kind != DoReply {
|
||||
t.Errorf("not called again once free: %+v", a)
|
||||
}
|
||||
if e.Status().Attempts != 0 {
|
||||
t.Errorf("attempts = %d on a fresh series, want 0", e.Status().Attempts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAFinalFrameToSomebodyElseIsNotABusyTarget(t *testing.T) {
|
||||
e := on()
|
||||
e.OnPeriod(period(0, cq("DX", NeedSlot, 0)))
|
||||
// Sending RR73 to another station: one period from being free, and the best
|
||||
// moment there is to be calling it.
|
||||
wrap := busy("DX", NeedSlot, 0)
|
||||
wrap.Msg = "PY2SAD DX RR73"
|
||||
if a := e.OnPeriod(period(2, wrap)); a.Kind != DoNothing || e.Target() != "DX" {
|
||||
t.Errorf("dropped a station that was finishing: %+v (target %q)", a, e.Target())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNothingHiddenByThePanelIsCalled(t *testing.T) {
|
||||
e := New(Settings{Enabled: true, OnScreenOnly: true})
|
||||
// The operator has filtered the list down to CQs: what is not on the screen
|
||||
// is not called, whatever the log makes of it.
|
||||
hidden := cq("RARE", NeedDXCC, 0)
|
||||
hidden.Hidden = true
|
||||
shown := cq("PLAIN", NeedSlot, -20)
|
||||
if a := e.OnPeriod(period(0, hidden, shown)); a.Decode.Call != "PLAIN" {
|
||||
t.Errorf("picked %q, want the station the panel is showing", a.Decode.Call)
|
||||
}
|
||||
// With the panel publishing nothing, nothing is hidden and the ladder
|
||||
// decides alone.
|
||||
e = New(Settings{Enabled: true, OnScreenOnly: true})
|
||||
free := cq("RARE", NeedDXCC, 0)
|
||||
if a := e.OnPeriod(period(2, free, shown)); a.Decode.Call != "RARE" {
|
||||
t.Errorf("picked %q with no filtering in force, want the new entity", a.Decode.Call)
|
||||
}
|
||||
}
|
||||
|
||||
// MSHV answers two stations in one transmission and the decoder prints them as
|
||||
// one line. Reported from the air: the DX was answering us in the second half
|
||||
// and the engine, reading only the first, dropped it as busy.
|
||||
func TestAMultiAnswerLineIsReadWhole(t *testing.T) {
|
||||
fox := cq("HK0/PY8WW", NeedDXCC, -17, watched)
|
||||
fox.CQ = false
|
||||
fox.Msg = "YV5ALI RR73; " + me + " <HK0/PY8WW> -08"
|
||||
|
||||
if !callingUs(fox, me) {
|
||||
t.Error("the second half is a report to us and was not read as one")
|
||||
}
|
||||
if !callable(fox, me) {
|
||||
t.Error("a station answering us was judged uncallable")
|
||||
}
|
||||
// Held as the target, that line must not release it — it is the answer we
|
||||
// were waiting for, not a station working somebody else.
|
||||
e := on()
|
||||
e.OnPeriod(period(0, cq("HK0/PY8WW", NeedDXCC, -17, watched)))
|
||||
if a := e.OnPeriod(period(2, fox)); a.Kind != DoNothing || e.Target() != "HK0/PY8WW" {
|
||||
t.Errorf("dropped the DX as it answered us: %+v (target %q)", a, e.Target())
|
||||
}
|
||||
// The exchange is under way, so the attempt counter has done its job.
|
||||
if e.Status().Attempts != 0 {
|
||||
t.Errorf("attempts = %d while in QSO, want 0", e.Status().Attempts)
|
||||
}
|
||||
// And its final frame to us, in the same shape, ends the QSO.
|
||||
done := fox
|
||||
done.Msg = "IK2ABC RR73; " + me + " <HK0/PY8WW> RR73"
|
||||
if !finished([]Candidate{done}, "HK0/PY8WW", me) {
|
||||
t.Error("a 73 to us inside a multi-answer line did not end the QSO")
|
||||
}
|
||||
}
|
||||
|
||||
// Reported from the air twice: the DX sends RR73, our own 73 is about to go
|
||||
// out, and the engine answered somebody else in that very slot — two
|
||||
// transmissions in one period, and the station at the other end never got the
|
||||
// frame that closes the contact.
|
||||
func TestTheSlotAfterAQSOBelongsToOur73(t *testing.T) {
|
||||
e := on()
|
||||
e.OnPeriod(period(0, cq("HP/WE9G", NeedDXCC, -6)))
|
||||
bye := callsMe("HP/WE9G", NeedDXCC, -6)
|
||||
bye.Msg = "<" + me + "> HP/WE9G RR73"
|
||||
// A new band is decoding in the same period and is not called.
|
||||
a := e.OnPeriod(period(2, bye, cq("GW8DX", NeedBand, +2)))
|
||||
if a.Kind != DoNothing {
|
||||
t.Fatalf("called %+v in the slot our 73 goes out in", a)
|
||||
}
|
||||
if !strings.Contains(a.Reason, "73") {
|
||||
t.Errorf("reason %q does not say why the slot was left alone", a.Reason)
|
||||
}
|
||||
// It is still there fifteen seconds later, which is the whole cost.
|
||||
if a := e.OnPeriod(period(4, cq("GW8DX", NeedBand, +2))); a.Kind != DoReply || a.Decode.Call != "GW8DX" {
|
||||
t.Errorf("next period: %+v, want the new band called", a)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user