github-actions[bot] commited on
Commit
a173f46
·
1 Parent(s): 6de4343

Automated sync from GitHub

Browse files
Files changed (4) hide show
  1. .gitattributes +1 -1
  2. Dockerfile +12 -5
  3. bot/speedtest.py +20 -7
  4. tests/test_speedtest.py +24 -11
.gitattributes CHANGED
@@ -1,3 +1,4 @@
 
1
  *.mp3 filter=lfs diff=lfs merge=lfs -text
2
  *.jpg filter=lfs diff=lfs merge=lfs -text
3
  *.jpeg filter=lfs diff=lfs merge=lfs -text
@@ -5,4 +6,3 @@
5
  *.ttf filter=lfs diff=lfs merge=lfs -text
6
  *.otf filter=lfs diff=lfs merge=lfs -text
7
  *.webp filter=lfs diff=lfs merge=lfs -text
8
- *.mp4 filter=lfs diff=lfs merge=lfs -text
 
1
+ *.mp4 filter=lfs diff=lfs merge=lfs -text
2
  *.mp3 filter=lfs diff=lfs merge=lfs -text
3
  *.jpg filter=lfs diff=lfs merge=lfs -text
4
  *.jpeg filter=lfs diff=lfs merge=lfs -text
 
6
  *.ttf filter=lfs diff=lfs merge=lfs -text
7
  *.otf filter=lfs diff=lfs merge=lfs -text
8
  *.webp filter=lfs diff=lfs merge=lfs -text
 
Dockerfile CHANGED
@@ -6,11 +6,18 @@ RUN apt-get update \
6
  && rm -rf /var/lib/apt/lists/*
7
 
8
  # Ookla speedtest CLI: /speed needs it. The version is pinned so a rebuild
9
- # cannot silently change the tool.
10
- RUN curl -sSL https://s3.amazonaws.com/ookla.speedtest.net/tools/cli/ookla-speedtest-1.2.0-linux-x86_64.tgz -o /tmp/ookla.tgz \
11
- && tar -xzf /tmp/ookla.tgz -C /tmp \
12
- && install -m 0755 /tmp/ookla-speedtest /usr/local/bin/ookla-speedtest \
13
- && rm -rf /tmp/ookla.tgz /tmp/ookla-speedtest
 
 
 
 
 
 
 
14
 
15
  # Hugging Face Spaces run Docker containers as user 1000
16
  RUN useradd -m -u 1000 user
 
6
  && rm -rf /var/lib/apt/lists/*
7
 
8
  # Ookla speedtest CLI: /speed needs it. The version is pinned so a rebuild
9
+ # cannot silently change the tool. The tarball unpacks to a binary called
10
+ # "speedtest"; it is installed as "ookla-speedtest" to match the bot.
11
+ RUN set -eux; \
12
+ case "$(uname -m)" in \
13
+ x86_64) arch=x86_64 ;; \
14
+ aarch64|arm64) arch=aarch64 ;; \
15
+ *) echo "unsupported architecture: $(uname -m)" >&2; exit 1 ;; \
16
+ esac; \
17
+ curl -fsSL --retry 3 "https://install.speedtest.net/app/cli/ookla-speedtest-1.2.0-linux-${arch}.tgz" -o /tmp/ookla.tgz; \
18
+ tar -xzf /tmp/ookla.tgz -C /tmp; \
19
+ install -m 0755 /tmp/speedtest /usr/local/bin/ookla-speedtest; \
20
+ rm -rf /tmp/ookla.tgz /tmp/speedtest
21
 
22
  # Hugging Face Spaces run Docker containers as user 1000
23
  RUN useradd -m -u 1000 user
bot/speedtest.py CHANGED
@@ -13,8 +13,9 @@ from dataclasses import dataclass
13
  from .utils import esc
14
 
15
  OOKLA_BIN = "ookla-speedtest"
16
- # --simple skips the server scan, --format=json makes the output readable
17
- OOKLA_ARGS = ("--accept-license", "--accept-gdpr", "--simple", "--format=json")
 
18
 
19
 
20
  @dataclass
@@ -27,9 +28,17 @@ class SpeedResult:
27
 
28
 
29
  def parse_ookla_json(raw: str) -> SpeedResult:
30
- """Read the JSON that ``ookla-speedtest --format=json`` prints."""
 
 
 
 
 
 
 
 
31
  try:
32
- data = json.loads(raw)
33
  except (TypeError, ValueError) as exc:
34
  raise ValueError("the speedtest output was not valid JSON") from exc
35
  if not isinstance(data, dict):
@@ -42,14 +51,18 @@ def parse_ookla_json(raw: str) -> SpeedResult:
42
 
43
  ping = data.get("ping") or {}
44
  try:
45
- ping_ms = float(ping.get("iqm") or ping.get("latency") or 0)
 
 
46
  except (TypeError, ValueError):
47
  ping_ms = 0.0
48
 
49
  server = data.get("server") or {}
50
  name = str(server.get("name") or "").strip()
 
51
  country = str(server.get("country") or "").strip()
52
- label = " — ".join(part for part in (name, country) if part)
 
53
  return SpeedResult(
54
  download_mbps=download / 1_000_000,
55
  upload_mbps=upload / 1_000_000,
@@ -75,7 +88,7 @@ def format_speed(result: SpeedResult) -> str:
75
  return "\n".join(lines)
76
 
77
 
78
- async def run_speedtest(binary: str = OOKLA_BIN, timeout: float = 120.0) -> SpeedResult:
79
  """Run the Ookla CLI and return the result.
80
 
81
  Raises ``FileNotFoundError`` when the binary is missing and
 
13
  from .utils import esc
14
 
15
  OOKLA_BIN = "ookla-speedtest"
16
+ # --accept-license / --accept-gdpr stop the tool waiting for a prompt; the
17
+ # licence notice goes to stderr, so stdout stays pure JSON.
18
+ OOKLA_ARGS = ("--accept-license", "--accept-gdpr", "--format=json")
19
 
20
 
21
  @dataclass
 
28
 
29
 
30
  def parse_ookla_json(raw: str) -> SpeedResult:
31
+ """Read the JSON that ``ookla-speedtest --format=json`` prints.
32
+
33
+ The CLI puts its licence notice on stderr, but be forgiving about anything
34
+ that ends up in front of the JSON.
35
+ """
36
+ text = (raw or "").strip()
37
+ start = text.find("{")
38
+ if start > 0:
39
+ text = text[start:]
40
  try:
41
+ data = json.loads(text)
42
  except (TypeError, ValueError) as exc:
43
  raise ValueError("the speedtest output was not valid JSON") from exc
44
  if not isinstance(data, dict):
 
51
 
52
  ping = data.get("ping") or {}
53
  try:
54
+ # the CLI reports the round-trip time as "latency"; "iqm" is the
55
+ # inter-quartile mean it uses for the per-phase numbers
56
+ ping_ms = float(ping.get("latency") or ping.get("iqm") or 0)
57
  except (TypeError, ValueError):
58
  ping_ms = 0.0
59
 
60
  server = data.get("server") or {}
61
  name = str(server.get("name") or "").strip()
62
+ location = str(server.get("location") or "").strip()
63
  country = str(server.get("country") or "").strip()
64
+ where = ", ".join(part for part in (location, country) if part)
65
+ label = " — ".join(part for part in (name, where) if part)
66
  return SpeedResult(
67
  download_mbps=download / 1_000_000,
68
  upload_mbps=upload / 1_000_000,
 
88
  return "\n".join(lines)
89
 
90
 
91
+ async def run_speedtest(binary: str = OOKLA_BIN, timeout: float = 180.0) -> SpeedResult:
92
  """Run the Ookla CLI and return the result.
93
 
94
  Raises ``FileNotFoundError`` when the binary is missing and
tests/test_speedtest.py CHANGED
@@ -2,19 +2,32 @@ import pytest
2
 
3
  from bot.speedtest import format_speed, parse_ookla_json
4
 
5
- RAW = """{"timestamp":"2024-01-01T00:00:00.000Z",
6
- "download":{"bandwidth":91234567,"bytes":1000,"elapsed":10000},
7
- "upload":{"bandwidth":4567890,"bytes":1000,"elapsed":10000},
8
- "ping":{"jitter":2,"iqm":12,"low":10,"high":15},
9
- "server":{"id":"1","host":"x","name":"Mumbai","country":"IN","sponsor":"ACME","d":0,"latency":12},
10
- "isp":"Example ISP","result":{"id":"1","url":"http://x","ok":true}}"""
 
 
 
 
 
 
11
 
12
 
13
  def test_parse_ookla_json_reads_the_speeds():
14
  r = parse_ookla_json(RAW)
15
- assert r.download_mbps == pytest.approx(91.234567, rel=1e-4)
16
- assert r.upload_mbps == pytest.approx(4.56789, rel=1e-4)
17
- assert r.ping_ms == 12 and r.server == "Mumbai — IN" and r.isp == "Example ISP"
 
 
 
 
 
 
 
18
 
19
 
20
  def test_parse_ookla_json_rejects_rubbish():
@@ -28,8 +41,8 @@ def test_parse_ookla_json_rejects_rubbish():
28
 
29
  def test_format_speed_shows_the_numbers():
30
  text = format_speed(parse_ookla_json(RAW))
31
- assert "91.23" in text and "4.57" in text and "12" in text
32
- assert "Mumbai" in text and "Example ISP" in text
33
 
34
 
35
  def test_format_speed_copes_with_missing_details():
 
2
 
3
  from bot.speedtest import format_speed, parse_ookla_json
4
 
5
+ # A real `ookla-speedtest --accept-license --accept-gdpr --format=json` run.
6
+ RAW = """{"type":"result","timestamp":"2026-10-20T10:45:15Z",
7
+ "ping":{"jitter":0.273,"latency":2.998,"low":2.833,"high":3.315},
8
+ "download":{"bandwidth":11034552,"bytes":39707592,"elapsed":3602,
9
+ "latency":{"iqm":16.372,"low":6.344,"high":48.044,"jitter":3.565}},
10
+ "upload":{"bandwidth":11886628,"bytes":162534754,"elapsed":14821,
11
+ "latency":{"iqm":128.627,"low":5.8,"high":523.355,"jitter":48.431}},
12
+ "packetLoss":0,"isp":"ACT Fibernet",
13
+ "interface":{"internalIp":"192.168.32.3","name":"wlan0","externalIp":"49.204.136.88"},
14
+ "server":{"id":60836,"host":"chn.infonetcomm.com","port":8080,"name":"ACT FIBERNET",
15
+ "location":"Chennai","country":"India","ip":"2406:7400:b0:1::2"},
16
+ "result":{"id":"18dc","url":"https://www.speedtest.net/result/c/18dc","persisted":true}}"""
17
 
18
 
19
  def test_parse_ookla_json_reads_the_speeds():
20
  r = parse_ookla_json(RAW)
21
+ assert r.download_mbps == pytest.approx(11.034552, rel=1e-4)
22
+ assert r.upload_mbps == pytest.approx(11.886628, rel=1e-4)
23
+ assert r.ping_ms == 2.998 # the CLI reports the round trip as "latency"
24
+ assert r.server == "ACT FIBERNET — Chennai, India"
25
+ assert r.isp == "ACT Fibernet"
26
+
27
+
28
+ def test_parse_ookla_json_survives_a_leading_licence_notice():
29
+ r = parse_ookla_json("You may only use this Speedtest software...\n" + RAW)
30
+ assert r.download_mbps == pytest.approx(11.034552, rel=1e-4)
31
 
32
 
33
  def test_parse_ookla_json_rejects_rubbish():
 
41
 
42
  def test_format_speed_shows_the_numbers():
43
  text = format_speed(parse_ookla_json(RAW))
44
+ assert "11.03" in text and "11.89" in text and "3" in text
45
+ assert "ACT FIBERNET" in text and "Chennai" in text and "ACT Fibernet" in text
46
 
47
 
48
  def test_format_speed_copes_with_missing_details():