diff --git a/.github/workflows/coveralls.yml b/.github/workflows/coveralls.yml new file mode 100644 index 0000000..e3add9c --- /dev/null +++ b/.github/workflows/coveralls.yml @@ -0,0 +1,63 @@ +# Created with GitHubActions version 0.2.16 +name: Coveralls Report +env: + GITHUB_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} +on: + - pull_request + - push +jobs: + linux: + name: Test on Ubuntu (Elixir ${{ matrix.elixir }}, OTP ${{ matrix.otp }}) + runs-on: ubuntu-20.04 + services: + # Label used to access the service container + strategy: + matrix: + elixir: + - '1.15.6' + otp: + - '26.1' + exclude: + - elixir: '1.13.4' + otp: '26.1' + - elixir: '1.14.5' + otp: '22.3' + - elixir: '1.14.5' + otp: '26.1' + - elixir: '1.15.6' + otp: '22.3' + - elixir: '1.15.6' + otp: '23.3' + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.otp }} + - name: Restore deps + uses: actions/cache@v3 + with: + path: deps + key: deps-${{ runner.os }}-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }} + - name: Restore _build + uses: actions/cache@v3 + with: + path: _build + key: _build-${{ runner.os }}-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }} + - name: Get dependencies + run: mix deps.get + - name: Compile dependencies + run: MIX_ENV=test mix deps.compile + - name: Compile project + run: MIX_ENV=test mix compile + - name: Check code format + if: ${{ contains(matrix.elixir, '1.15.6') && contains(matrix.otp, '26.1') }} + run: mix format --check-formatted + - name: Run tests + run: MIX_ENV=test mix test + if: ${{ !(contains(matrix.elixir, '1.15.6') && contains(matrix.otp, '26.1')) }} + - name: Run tests with coverage + run: MIX_ENV=test mix coveralls.github + if: ${{ contains(matrix.elixir, '1.15.6') && contains(matrix.otp, '26.1') }} \ No newline at end of file diff --git a/.github/workflows/increment_version.yml b/.github/workflows/increment_version.yml new file mode 100644 index 0000000..9075203 --- /dev/null +++ b/.github/workflows/increment_version.yml @@ -0,0 +1,21 @@ +name: "Increment Product Version" +on: + workflow_dispatch: + + push: + branches: + - master +jobs: + Increment-Version: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Bump version and push tag + uses: anothrNick/github-tag-action@1.36.0 + env: + GITHUB_TOKEN: ${{ github.token }} + WITH_V: true diff --git a/.github/workflows/vulnerability_scan.yml b/.github/workflows/vulnerability_scan.yml new file mode 100644 index 0000000..640f9c5 --- /dev/null +++ b/.github/workflows/vulnerability_scan.yml @@ -0,0 +1,71 @@ +name: Execute Vulnerability Scan +on: + workflow_dispatch: + schedule: + - cron: "0 12 * * 5" + +env: + ELIXIR_VERSION: "1.15" + OTP_VERSION: "24.2.1" + APPLICATION_WORKING_DIR: ./ + +jobs: + execute_scan: + name: Execute Vulnerability Scan + runs-on: ubuntu-latest + steps: + - name: Checkout Github Repo + uses: actions/checkout@v3 + + - name: Setup Elixir + uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + + - name: Setup Dependencies Cache + id: deps-cache + run: | + echo "key=${{ runner.os }}-Elixir(${{ env.OTP_VERSION }})-OTP(${{ env.ELIXIR_VERSION }})-Mix.LockHash(${{ hashFiles('**/mix.lock') }})" >> $GITHUB_OUTPUT + + - name: Retrieve Cached Dependencies + uses: actions/cache@v3 + id: mix-cache + with: + path: | + ${{env.APPLICATION_WORKING_DIR}}/deps + ${{env.APPLICATION_WORKING_DIR}}/_build + key: ${{ steps.deps-cache.outputs.key }} + + - name: Install Dependencies + if: steps.mix-cache.outputs.cache-hit != 'true' + working-directory: ${{env.APPLICATION_WORKING_DIR}} + run: | + mix local.rebar --force + mix local.hex --force + mix deps.get + mix deps.compile + + - name: Execute Report + id: report_status + working-directory: ${{env.APPLICATION_WORKING_DIR}} + run: | + mix deps.audit + + - name: Echo Scan Exit Code + if: always() + run: echo "${{steps.report_status.outcome}}" + + - name: Vulnerabilities Found - Discord notification + if: always() && steps.report_status.outcome == 'failure' + uses: sarisia/actions-status-discord@v1 + with: + webhook: ${{ secrets.DISCORD_WEBHOOK }} + status: 'failure' + username: GitHub Actions + avatar_url: ${{ secrets.DISCORD_AVATAR_URL }} + title: " Vulnerability Audit" + description: "View Scan Results: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + image: ${{ secrets.DISCORD_EMBED_IMAGE }} + color: 0x0000ff + url: ${{ github.server_url }}/${{ github.repository }} diff --git a/README.md b/README.md index 4e2d25f..7688a02 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ by adding `speedtest` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:speedtest, "~> 1.1.0"} + {:speedtest, "~> 1.1.1"} ] end ``` diff --git a/coveralls.json b/coveralls.json new file mode 100644 index 0000000..c410370 --- /dev/null +++ b/coveralls.json @@ -0,0 +1,6 @@ +{ + "skip_files": [ + "deps", + "config" + ] +} \ No newline at end of file diff --git a/lib/result.ex b/lib/result.ex index 9524d07..3bda2a8 100644 --- a/lib/result.ex +++ b/lib/result.ex @@ -36,12 +36,16 @@ defmodule Speedtest.Result do upload_total_time_in_sec = Enum.sum(upload_times) upload_size_total_bytes = Enum.sum(upload_sizes) - upload_size_total_mb = upload_size_total_bytes / 125_000 - upload = upload_size_total_mb / upload_total_time_in_sec + upload_size_total_mb = upload_size_total_bytes / 1_000_000 + + upload_size_total_mbit = upload_size_total_mb * 8 + + upload = upload_size_total_mbit / upload_total_time_in_sec upload_avg_sec = upload_total_time_in_sec / Enum.count(upload_reply) - upload_avg_sec = upload_size_total_bytes / upload_avg_sec + + upload_bps = upload_size_total_bytes / upload_avg_sec download_times = Enum.map(download_reply, fn x -> @@ -56,15 +60,19 @@ defmodule Speedtest.Result do download_time_in_sec = Enum.sum(download_times) download_size_total_bytes = Enum.sum(download_sizes) - download_size_total_mb = download_size_total_bytes / 125_000 - download = download_size_total_mb / download_time_in_sec + download_size_total_mb = download_size_total_bytes / 1_000_000 + + download_size_total_mbit = download_size_total_mb * 8 + + download = download_size_total_mbit / download_time_in_sec download_avg_sec = download_time_in_sec / Enum.count(download_reply) - download_avg_sec = download_size_total_bytes / download_avg_sec - client = %{speedtest.config.client | ispdlavg: trunc(Float.round(download_avg_sec))} - client = %{client | ispulavg: trunc(Float.round(upload_avg_sec))} + download_bps = download_size_total_bytes / download_avg_sec + + client = %{speedtest.config.client | ispdlavg: trunc(Float.round(download_bps))} + client = %{client | ispulavg: trunc(Float.round(upload_bps))} result = %Result{ download: Float.round(download, 2), @@ -108,9 +116,9 @@ defmodule Speedtest.Result do ) |> Base.encode16() - download = round(result.download / 1000.0) + download = round(result.download) ping = round(ping) - upload = round(result.upload / 1000.0) + upload = round(result.upload) api_data = [ "recommendedserverid=" <> to_string(result.server.id), diff --git a/lib/speedtest.ex b/lib/speedtest.ex index 8bce4d0..77a6d40 100644 --- a/lib/speedtest.ex +++ b/lib/speedtest.ex @@ -11,6 +11,8 @@ defmodule Speedtest do require Logger + @ttl 25000 + defstruct config: [], servers: [], include: nil, @@ -23,9 +25,9 @@ defmodule Speedtest do Retrieve a the list of speedtest.net servers, optionally filtered to servers matching those specified in the servers argument - example: + ## Examples - Speedtest.fetch_servers(%Speedtest{}) + iex> Speedtest.fetch_servers(%Speedtest{}) """ def fetch_servers(%Speedtest{} = speedtest \\ %Speedtest{}) do @@ -64,99 +66,131 @@ defmodule Speedtest do Limit servers to the closest speedtest.net servers based on geographic distance - example: + ## Examples - Speedtest.choose_closest_servers() + iex> Speedtest.choose_closest_servers() """ def choose_closest_servers(servers \\ [], amount \\ 2) do - servers = Enum.sort_by(servers, fn s -> s.distance end) - reply = Enum.take(servers, amount) - reply + Enum.sort_by(servers, fn s -> s.distance end) + |> Enum.take(amount) end @doc """ Perform a speedtest.net "ping" to determine which speedtest.net server has the lowest latency - example: + ## Examples - Speedtest.choose_best_server([]}) + iex> Speedtest.choose_best_server([]) """ def choose_best_server(servers) do Logger.info("Selecting best server based on ping...") - reply = - Enum.map(servers, fn s -> - url = Decoder.url(s.host) - ping = Speedtest.Ping.ping(url) - Map.put(s, :ping, ping) - end) - - servers = Enum.sort_by(reply, fn s -> s.ping end) - - List.first(servers) + Enum.map(servers, fn s -> + url = Decoder.url(s.host) + ping = Speedtest.Ping.ping(url) + Map.put(s, :ping, ping) + end) + |> Enum.sort_by(fn s -> s.ping end) + |> List.first(servers) end @doc """ Test download speed against speedtest.net - example: + ## Examples - Speedtest.download(%Speedtest{}) + iex> Speedtest.download(%Speedtest{}) """ def download(%Speedtest{} = speedtest \\ %Speedtest{}) do Logger.info("Testing download speed...") {_, urls} = generate_download_urls(speedtest) + threads = + case is_nil(speedtest.threads) do + true -> 1 + false -> String.to_integer(speedtest.threads) + end + + urls = Enum.chunk_every(urls, threads) + responses = - Enum.map(urls, fn u -> - {time_in_microseconds, return} = - :timer.tc(fn -> - {_, reply} = HTTPoison.get(u) - reply - end) - - [{_, length}] = - Enum.filter(return.headers, fn h -> - {key, _} = h - key == "Content-Length" - end) - - time_in_seconds = time_in_microseconds / 1_000_000 - - %{elapsed_time: time_in_seconds, bytes: String.to_integer(length), url: u} + Enum.map(urls, fn data -> + Enum.map(data, fn url -> + timed_fetch(url) + end) + |> Task.await_many(@ttl) end) + |> List.flatten() {:ok, responses} end + defp timed_fetch(url) do + Task.async(fn -> + {time_in_microseconds, return} = + :timer.tc(fn -> + {_, reply} = HTTPoison.get(url, [], recv_timeout: @ttl) + reply + end) + + [{_, length}] = + Enum.filter(return.headers, fn h -> + {key, _} = h + key == "Content-Length" + end) + + time_in_seconds = time_in_microseconds / 1_000_000 + + %{elapsed_time: time_in_seconds, bytes: String.to_integer(length), url: url} + end) + end + + defp timed_post(url, size) do + Task.async(fn -> + event_time = System.monotonic_time(:millisecond) + + headers = [{"Content-length", size}] + body = "" + HTTPoison.post(url, body, headers, recv_timeout: @ttl) + + time_in_milliseconds = System.monotonic_time(:millisecond) - event_time + + %{elapsed_time: time_in_milliseconds / 1_000, bytes: size, url: url} + end) + end + @doc """ Test upload speed against speedtest.net - example: + ## Examples - Speedtest.upload(%Speedtest{}) + iex> Speedtest.upload(%Speedtest{}) """ def upload(%Speedtest{} = speedtest \\ %Speedtest{}) do Logger.info("Testing Upload Speed...") {_, data} = generate_upload_data(speedtest) - responses = - Enum.map(data, fn {url, size} -> - event_time = System.monotonic_time(:millisecond) - - headers = [{"Content-length", size}] - body = "" - {_, reply} = HTTPoison.post(url, body, headers) + threads = + case is_nil(speedtest.threads) do + true -> 1 + false -> String.to_integer(speedtest.threads) + end - time_in_milliseconds = System.monotonic_time(:millisecond) - event_time + data = Enum.chunk_every(data, threads) - %{elapsed_time: time_in_milliseconds / 1_000, bytes: size, url: url} + responses = + Enum.map(data, fn data -> + Enum.map(data, fn {url, size} -> + timed_post(url, size) + end) + |> Task.await_many(@ttl) end) + |> List.flatten() {:ok, responses} end @@ -164,9 +198,9 @@ defmodule Speedtest do @doc """ Determine distance between sets of [lat,lon] in km - example: + ## Examples - Speedtest.distance(%Speedtest{}) + iex> Speedtest.distance(%Speedtest{}) """ def distance(%Speedtest{} = speedtest \\ %Speedtest{}) do @@ -190,9 +224,9 @@ defmodule Speedtest do @doc """ Run the full speedtest.net test - example: + ## Examples - Speedtest.run() + iex> Speedtest.run() """ def run() do @@ -240,10 +274,10 @@ defmodule Speedtest do {_, reply} = Result.create(speedtest, replys) - speed = to_string(Float.round(reply.result.download, 2)) <> " Mbit/s" + speed = to_string(reply.result.download) <> " Mbit/s" Logger.info("Download: " <> speed) - speed = to_string(Float.round(reply.result.upload, 2)) <> " Mbit/s" + speed = to_string(reply.result.upload) <> " Mbit/s" Logger.info("Upload: " <> speed) config = reply.config @@ -260,9 +294,9 @@ defmodule Speedtest do @doc """ Ping an IP and return a tuple with the time - example: + ## Examples - Speedtest.ping("127.0.0.1") + iex> Speedtest.ping("127.0.0.1") """ def ping(ip) do @@ -274,8 +308,8 @@ defmodule Speedtest do ## Examples - iex> Speedtest.init() - {:ok, %Speedtest{config: [],exclude: nil,include: nil,result: nil,selected_server: nil,servers: [],threads: nil}} + iex> Speedtest.init() + {:ok, %Speedtest{config: [],exclude: nil,include: nil,result: nil,selected_server: nil,servers: [],threads: nil}} """ @@ -288,7 +322,7 @@ defmodule Speedtest do {:ok, reply} end - defp generate_download_urls(%Speedtest{} = speedtest \\ %Speedtest{}) do + defp generate_download_urls(%Speedtest{} = speedtest) do urls = Enum.map(speedtest.config.sizes.download, fn s -> size = to_string(s) @@ -300,7 +334,7 @@ defmodule Speedtest do {:ok, urls} end - defp generate_upload_data(%Speedtest{} = speedtest \\ %Speedtest{}) do + defp generate_upload_data(%Speedtest{} = speedtest) do data = Enum.map(speedtest.config.sizes.upload, fn s -> {speedtest.selected_server.url, to_string(s)} diff --git a/mix.exs b/mix.exs index 0e70d58..16ded44 100644 --- a/mix.exs +++ b/mix.exs @@ -8,7 +8,7 @@ defmodule Speedtest.MixProject do [ app: :speedtest, version: @version, - elixir: "~> 1.9", + elixir: "~> 1.11", build_embedded: Mix.env() == :prod, start_permanent: Mix.env() == :prod, description: description(), @@ -30,12 +30,12 @@ defmodule Speedtest.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ex_doc, "~> 0.25", only: :dev, runtime: false}, - {:httpoison, "~> 1.8"}, - {:sweet_xml, "~> 0.7"}, - {:geocalc, "~> 0.8"}, - {:inch_ex, ">= 0.0.0", only: [:test, :dev]}, - {:mix_test_watch, "~> 0.8", only: :dev, runtime: false} + {:ex_doc, "~> 0.30.9", only: :dev, runtime: false}, + {:httpoison, "~> 2.2"}, + {:sweet_xml, "~> 0.7.4"}, + {:geocalc, "~> 0.8.5"}, + {:inch_ex, only: :docs}, + {:mix_audit, ">= 0.0.0", only: [:dev, :test], runtime: false} ] end diff --git a/mix.lock b/mix.lock index d3b03fa..1f396b1 100644 --- a/mix.lock +++ b/mix.lock @@ -1,28 +1,32 @@ %{ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, - "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "earmark": {:hex, :earmark, "1.3.5", "0db71c8290b5bc81cb0101a2a507a76dca659513984d683119ee722828b424f6", [:mix], [], "hexpm"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.15", "b29e8e729f4aa4a00436580dcc2c9c5c51890613457c193cc8525c388ccb2f06", [:mix], [], "hexpm", "044523d6438ea19c1b8ec877ec221b008661d3c27e3b848f4c879f500421ca5c"}, - "ex_doc": {:hex, :ex_doc, "0.25.2", "4f1cae793c4d132e06674b282f1d9ea3bf409bcca027ddb2fe177c4eed6a253f", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5b0c172e87ac27f14dfd152d52a145238ec71a95efbf29849550278c58a393d6"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, + "ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "geocalc": {:hex, :geocalc, "0.8.4", "ee0526cccccbcd52498670e20852b0b763edbfc67c5225c33dfebd3be7bd5393", [:mix], [], "hexpm", "d58cbfc54fee549340c790efc54321e68fbc92d9dc334e24cef4afc120fefc8c"}, + "geocalc": {:hex, :geocalc, "0.8.5", "b9886679e44c323e5b72dcd90a64f834d775d2600af0b656ea9f07ccdacaa5a6", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "3870c25c78513ec0456b69324c2be1af2202961002e81fb659559e3db162c802"}, "google_maps": {:hex, :google_maps, "0.11.0", "43ff0e555405f6ac2bdb805be70b8d5ef72a412fc8cbf5d4eea8471d73ff8152", [:mix], [{:httpoison, "~> 1.5", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, - "hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"}, - "httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"}, + "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "inch_ex": {:hex, :inch_ex, "2.0.0", "24268a9284a1751f2ceda569cd978e1fa394c977c45c331bb52a405de544f4de", [:mix], [{:bunt, "~> 0.2", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "96d0ec5ecac8cf63142d02f16b7ab7152cf0f0f1a185a80161b758383c9399a8"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, - "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mix_audit": {:hex, :mix_audit, "2.1.1", "653aa6d8f291fc4b017aa82bdb79a4017903902ebba57960ef199cbbc8c008a1", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "541990c3ab3a7bb8c4aaa2ce2732a4ae160ad6237e5dcd5ad1564f4f85354db1"}, "mix_test_watch": {:hex, :mix_test_watch, "0.9.0", "c72132a6071261893518fa08e121e911c9358713f62794a90c95db59042af375", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "817dec4a7f6edf260258002f99ac8ffaf7a8f395b27bf2d13ec24018beecec8a"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, - "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, - "sweet_xml": {:hex, :sweet_xml, "0.7.1", "a2cac8e2101237e617dfa9d427d44b8aff38ba6294f313ffb4667524d6b71b98", [:mix], [], "hexpm", "8bc7b7b584a6a87113071d0d2fd39fe2251cf2224ecaeed7093bdac1b9c1555f"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, + "yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"}, } diff --git a/test/speedtest_test.exs b/test/speedtest_test.exs index 46fc791..71c75b1 100644 --- a/test/speedtest_test.exs +++ b/test/speedtest_test.exs @@ -1,6 +1,5 @@ defmodule SpeedtestTest do use ExUnit.Case - doctest Speedtest test "Fetch Speedtest.net servers" do {status, _} = Speedtest.fetch_servers()