diff --git a/test/microwaveprop/buildings/index_test.exs b/test/microwaveprop/buildings/index_test.exs index 4ff5a52b..d495b4e6 100644 --- a/test/microwaveprop/buildings/index_test.exs +++ b/test/microwaveprop/buildings/index_test.exs @@ -39,7 +39,7 @@ defmodule Microwaveprop.Buildings.IndexTest do records = Index.records_near(32.8000, -97.0000, 50) assert length(records) == 2 - heights = Enum.map(records, & &1.height_m) |> Enum.sort() + heights = records |> Enum.map(& &1.height_m) |> Enum.sort() assert heights == [8.0, 25.0] end diff --git a/test/microwaveprop/buildings/loader_test.exs b/test/microwaveprop/buildings/loader_test.exs index 0def1d4c..07c6cff4 100644 --- a/test/microwaveprop/buildings/loader_test.exs +++ b/test/microwaveprop/buildings/loader_test.exs @@ -13,8 +13,10 @@ defmodule Microwaveprop.Buildings.LoaderTest do on_exit(fn -> File.rm_rf(tmp) - if prev, do: Application.put_env(:microwaveprop, :buildings_cache_dir, prev), - else: Application.delete_env(:microwaveprop, :buildings_cache_dir) + + if prev, + do: Application.put_env(:microwaveprop, :buildings_cache_dir, prev), + else: Application.delete_env(:microwaveprop, :buildings_cache_dir) end) :ok @@ -37,8 +39,10 @@ defmodule Microwaveprop.Buildings.LoaderTest do File.mkdir_p!(cache_dir) geojsonl = - ~s|{"type": "Feature", "properties": {"height": 5.5}, "geometry": {"type": "Polygon", "coordinates": [[[-97.736, 32.221], [-97.736, 32.222], [-97.735, 32.222], [-97.735, 32.221], [-97.736, 32.221]]]}}| - |> then(&:zlib.gzip/1) + then( + ~s|{"type": "Feature", "properties": {"height": 5.5}, "geometry": {"type": "Polygon", "coordinates": [[[-97.736, 32.221], [-97.736, 32.222], [-97.735, 32.222], [-97.735, 32.221], [-97.736, 32.221]]]}}|, + &:zlib.gzip/1 + ) File.write!(Path.join(cache_dir, "#{quadkey}.csv.gz"), geojsonl) diff --git a/test/microwaveprop/commercial/snmp_client_test.exs b/test/microwaveprop/commercial/snmp_client_test.exs index f4059046..97caaa40 100644 --- a/test/microwaveprop/commercial/snmp_client_test.exs +++ b/test/microwaveprop/commercial/snmp_client_test.exs @@ -287,10 +287,13 @@ defmodule Microwaveprop.Commercial.SnmpClientTest do describe "poll/3 with injected snmp_runner" do setup do prev = Application.get_env(:microwaveprop, :snmp_runner) + on_exit(fn -> - if prev, do: Application.put_env(:microwaveprop, :snmp_runner, prev), - else: Application.delete_env(:microwaveprop, :snmp_runner) + if prev, + do: Application.put_env(:microwaveprop, :snmp_runner, prev), + else: Application.delete_env(:microwaveprop, :snmp_runner) end) + :ok end diff --git a/test/microwaveprop/propagation/path_compute_test.exs b/test/microwaveprop/propagation/path_compute_test.exs index f8df14ed..2f7951fe 100644 --- a/test/microwaveprop/propagation/path_compute_test.exs +++ b/test/microwaveprop/propagation/path_compute_test.exs @@ -67,8 +67,10 @@ defmodule Microwaveprop.Propagation.PathComputeTest do describe "compute_loss_budget/5 property" do property "total is always >= fspl" do - check all dist_km <- float(min: 0.1, max: 500), - band_config = BandConfig.get(10_000) do + check all( + dist_km <- float(min: 0.1, max: 500), + band_config = BandConfig.get(10_000) + ) do result = PathCompute.compute_loss_budget(dist_km, 10.0, band_config, nil, nil) assert result.total >= result.fspl assert result.total > 0 @@ -78,8 +80,10 @@ defmodule Microwaveprop.Propagation.PathComputeTest do property "all loss components are non-negative across known bands" do bands = [50, 144, 222, 432, 902, 1_296, 2_304, 3_400, 5_760, 10_000, 24_000, 47_000, 75_000] - check all dist_km <- float(min: 0.1, max: 500), - band_mhz <- member_of(bands) do + check all( + dist_km <- float(min: 0.1, max: 500), + band_mhz <- member_of(bands) + ) do band_config = BandConfig.get(band_mhz) freq_ghz = band_mhz / 1000 result = PathCompute.compute_loss_budget(dist_km, freq_ghz, band_config, nil, nil) diff --git a/test/microwaveprop/rover/compute_test.exs b/test/microwaveprop/rover/compute_test.exs index 9042347f..8c9f08bf 100644 --- a/test/microwaveprop/rover/compute_test.exs +++ b/test/microwaveprop/rover/compute_test.exs @@ -239,8 +239,8 @@ defmodule Microwaveprop.Rover.ComputeTest do cell_scores = Map.new(result.cells, fn c -> {{c.lat, c.lon}, c.score} end) penalized_score = cell_scores[cell_with_penalty] - other_scores = Map.drop(cell_scores, [cell_with_penalty]) |> Map.values() - assert penalized_score != nil + other_scores = cell_scores |> Map.delete(cell_with_penalty) |> Map.values() + assert penalized_score assert Enum.all?(other_scores, fn s -> s > penalized_score end) after Application.put_env(:microwaveprop, :rover_road_proximity_enabled, true) diff --git a/test/microwaveprop/rover/elevation_test.exs b/test/microwaveprop/rover/elevation_test.exs index 557cd1ad..b67106ea 100644 --- a/test/microwaveprop/rover/elevation_test.exs +++ b/test/microwaveprop/rover/elevation_test.exs @@ -72,7 +72,7 @@ defmodule Microwaveprop.Rover.ElevationTest do test "returns nil for SRTM void sentinel values (-32768)" do tile_path = Path.join(Application.get_env(:microwaveprop, :srtm_tiles_dir), "N00E000.hgt") # SRTM void sentinel is -32768 - File.write!(tile_path, <<(-32_768)::signed-big-integer-size(16)>>) + File.write!(tile_path, <<-32_768::signed-big-integer-size(16)>>) result = Elevation.lookup_many([{0.9999, 0.0}]) assert result[{0.9999, 0.0}] == nil diff --git a/test/microwaveprop/rover/prominence_test.exs b/test/microwaveprop/rover/prominence_test.exs index 99b8ce6f..9d8786d0 100644 --- a/test/microwaveprop/rover/prominence_test.exs +++ b/test/microwaveprop/rover/prominence_test.exs @@ -6,6 +6,7 @@ defmodule Microwaveprop.Rover.ProminenceTest do describe "prominence_map/2" do test "returns map keyed by {lat, lon}" do cells = [%{lat: 33.0, lon: -97.0}, %{lat: 33.1, lon: -97.1}] + elev_lookup = fn points -> Map.new(points, fn p -> {p, 200} end) end diff --git a/test/microwaveprop/terrain/viewshed_test.exs b/test/microwaveprop/terrain/viewshed_test.exs index 972fe039..2fb85f42 100644 --- a/test/microwaveprop/terrain/viewshed_test.exs +++ b/test/microwaveprop/terrain/viewshed_test.exs @@ -240,10 +240,12 @@ defmodule Microwaveprop.Terrain.ViewshedTest do use ExUnitProperties property "short bearings round-trip for CONUS-mid latitudes" do - check all lat <- float(min: 25, max: 50), - lon <- float(min: -125, max: -65), - bearing <- float(min: 0, max: 360), - dist_km <- float(min: 0.1, max: 20) do + check all( + lat <- float(min: 25, max: 50), + lon <- float(min: -125, max: -65), + bearing <- float(min: 0, max: 360), + dist_km <- float(min: 0.1, max: 20) + ) do {lat2, lon2} = Viewshed.destination_point(lat, lon, bearing, dist_km) back_bearing = if(bearing < 180, do: bearing + 180, else: bearing - 180) {lat3, lon3} = Viewshed.destination_point(lat2, lon2, back_bearing, dist_km) @@ -253,8 +255,10 @@ defmodule Microwaveprop.Terrain.ViewshedTest do end property "east-west bearing preserves latitude at equator" do - check all lon <- float(min: -170, max: 170), - dist_km <- float(min: 1, max: 50) do + check all( + lon <- float(min: -170, max: 170), + dist_km <- float(min: 1, max: 50) + ) do {lat2, lon2} = Viewshed.destination_point(0.0, lon, 90, dist_km) assert_in_delta lat2, 0.0, 0.01 assert lon2 != lon diff --git a/test/microwaveprop/valkey_test.exs b/test/microwaveprop/valkey_test.exs index 245b120e..270f7936 100644 --- a/test/microwaveprop/valkey_test.exs +++ b/test/microwaveprop/valkey_test.exs @@ -4,22 +4,24 @@ defmodule Microwaveprop.ValkeyTest do import Mox alias Microwaveprop.Valkey - # Register Mox mock as the adapter and fake a Redix connection so # configured?() returns true, letting command/pipeline run through the mock. + alias Microwaveprop.Valkey.Conn + alias Microwaveprop.Valkey.MockAdapter + setup do prev_url = Application.get_env(:microwaveprop, :valkey_url) prev_adapter = Application.get_env(:microwaveprop, :valkey_adapter) Application.put_env(:microwaveprop, :valkey_url, "redis://localhost:6379") - Application.put_env(:microwaveprop, :valkey_adapter, Microwaveprop.Valkey.MockAdapter) + Application.put_env(:microwaveprop, :valkey_adapter, MockAdapter) # Start a dummy process registered at the connection name. {:ok, pid} = Task.start(fn -> Process.sleep(:infinity) end) - Process.register(pid, Microwaveprop.Valkey.Conn) + Process.register(pid, Conn) on_exit(fn -> - Process.unregister(Microwaveprop.Valkey.Conn) + Process.unregister(Conn) Process.exit(pid, :kill) if prev_url, @@ -52,7 +54,7 @@ defmodule Microwaveprop.ValkeyTest do describe "get/1" do test "returns :miss when key is nil" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["GET", "missing"], _opts -> + expect(MockAdapter, :command, fn _conn, ["GET", "missing"], _opts -> {:ok, nil} end) @@ -60,7 +62,7 @@ defmodule Microwaveprop.ValkeyTest do end test "returns decoded term on hit" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["GET", "key"], _opts -> + expect(MockAdapter, :command, fn _conn, ["GET", "key"], _opts -> {:ok, :erlang.term_to_binary(%{foo: "bar"})} end) @@ -68,7 +70,7 @@ defmodule Microwaveprop.ValkeyTest do end test "propagates error" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["GET", "key"], _opts -> + expect(MockAdapter, :command, fn _conn, ["GET", "key"], _opts -> {:error, :connection_lost} end) @@ -82,7 +84,7 @@ defmodule Microwaveprop.ValkeyTest do end test "returns decoded values preserving order" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["MGET", "a", "b"], _opts -> + expect(MockAdapter, :command, fn _conn, ["MGET", "a", "b"], _opts -> {:ok, [nil, :erlang.term_to_binary(42)]} end) @@ -90,7 +92,7 @@ defmodule Microwaveprop.ValkeyTest do end test "propagates error" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["MGET", "key"], _opts -> + expect(MockAdapter, :command, fn _conn, ["MGET", "key"], _opts -> {:error, :connection_lost} end) @@ -100,7 +102,7 @@ defmodule Microwaveprop.ValkeyTest do describe "set/3" do test "returns :ok on success" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["SET", "k", _, "EX", "3600"], _opts -> + expect(MockAdapter, :command, fn _conn, ["SET", "k", _, "EX", "3600"], _opts -> {:ok, "OK"} end) @@ -108,7 +110,7 @@ defmodule Microwaveprop.ValkeyTest do end test "propagates error" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["SET", "k", _, "EX", "3600"], _opts -> + expect(MockAdapter, :command, fn _conn, ["SET", "k", _, "EX", "3600"], _opts -> {:error, :connection_lost} end) @@ -116,7 +118,7 @@ defmodule Microwaveprop.ValkeyTest do end test "handles unexpected response" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["SET", "k", _, "EX", "3600"], _opts -> + expect(MockAdapter, :command, fn _conn, ["SET", "k", _, "EX", "3600"], _opts -> {:ok, "QUEUED"} end) @@ -130,9 +132,9 @@ defmodule Microwaveprop.ValkeyTest do end test "returns :ok when all SETs succeed" do - expect(Microwaveprop.Valkey.MockAdapter, :pipeline, fn _conn, cmds, _opts -> + expect(MockAdapter, :pipeline, fn _conn, cmds, _opts -> assert length(cmds) == 2 - assert hd(cmds) |> List.first() == "SET" + assert cmds |> hd() |> List.first() == "SET" {:ok, ["OK", "OK"]} end) @@ -140,7 +142,7 @@ defmodule Microwaveprop.ValkeyTest do end test "returns {:error, {:partial, _}} when some SETs fail" do - expect(Microwaveprop.Valkey.MockAdapter, :pipeline, fn _conn, _cmds, _opts -> + expect(MockAdapter, :pipeline, fn _conn, _cmds, _opts -> {:ok, ["OK", "ERROR"]} end) @@ -148,7 +150,7 @@ defmodule Microwaveprop.ValkeyTest do end test "propagates pipeline error" do - expect(Microwaveprop.Valkey.MockAdapter, :pipeline, fn _conn, _cmds, _opts -> + expect(MockAdapter, :pipeline, fn _conn, _cmds, _opts -> {:error, :connection_lost} end) @@ -158,7 +160,7 @@ defmodule Microwaveprop.ValkeyTest do describe "zadd/3" do test "returns :ok on success" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["ZADD", "zset", "1.5", "mem"], _opts -> + expect(MockAdapter, :command, fn _conn, ["ZADD", "zset", "1.5", "mem"], _opts -> {:ok, 1} end) @@ -166,7 +168,7 @@ defmodule Microwaveprop.ValkeyTest do end test "propagates error" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["ZADD", "zset", "1.0", "mem"], _opts -> + expect(MockAdapter, :command, fn _conn, ["ZADD", "zset", "1.0", "mem"], _opts -> {:error, :connection_lost} end) @@ -176,7 +178,7 @@ defmodule Microwaveprop.ValkeyTest do describe "zrevrange/3" do test "returns members list" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["ZREVRANGE", "zset", "0", "10"], _opts -> + expect(MockAdapter, :command, fn _conn, ["ZREVRANGE", "zset", "0", "10"], _opts -> {:ok, ["a", "b"]} end) @@ -184,7 +186,7 @@ defmodule Microwaveprop.ValkeyTest do end test "propagates error" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, _, _opts -> + expect(MockAdapter, :command, fn _conn, _, _opts -> {:error, :connection_lost} end) @@ -194,9 +196,7 @@ defmodule Microwaveprop.ValkeyTest do describe "zrangebyscore/3" do test "returns members list" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, - ["ZRANGEBYSCORE", "zset", "0", "100"], - _opts -> + expect(MockAdapter, :command, fn _conn, ["ZRANGEBYSCORE", "zset", "0", "100"], _opts -> {:ok, ["a", "b"]} end) @@ -210,7 +210,7 @@ defmodule Microwaveprop.ValkeyTest do end test "removes members" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["ZREM", "zset", "a", "b"], _opts -> + expect(MockAdapter, :command, fn _conn, ["ZREM", "zset", "a", "b"], _opts -> {:ok, 2} end) @@ -224,7 +224,7 @@ defmodule Microwaveprop.ValkeyTest do end test "deletes keys" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["DEL", "k1", "k2"], _opts -> + expect(MockAdapter, :command, fn _conn, ["DEL", "k1", "k2"], _opts -> {:ok, 2} end) @@ -234,7 +234,7 @@ defmodule Microwaveprop.ValkeyTest do describe "scan_match/1" do test "returns all matching keys from single pass" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["SCAN", "0", "MATCH", "pfx:*", "COUNT", "500"], _opts -> + expect(MockAdapter, :command, fn _conn, ["SCAN", "0", "MATCH", "pfx:*", "COUNT", "500"], _opts -> {:ok, ["0", ["pfx:a", "pfx:b"]]} end) @@ -242,7 +242,7 @@ defmodule Microwaveprop.ValkeyTest do end test "follows cursor across multiple passes" do - Microwaveprop.Valkey.MockAdapter + MockAdapter |> expect(:command, fn _conn, ["SCAN", "0", "MATCH", "pfx:*", "COUNT", "500"], _opts -> {:ok, ["3", ["pfx:a"]]} end) @@ -254,7 +254,7 @@ defmodule Microwaveprop.ValkeyTest do end test "propagates error" do - expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, _, _opts -> + expect(MockAdapter, :command, fn _conn, _, _opts -> {:error, :connection_lost} end) diff --git a/test/microwaveprop/weather/grib2/wgrib2_test.exs b/test/microwaveprop/weather/grib2/wgrib2_test.exs index 7c1e3530..dbea7cd7 100644 --- a/test/microwaveprop/weather/grib2/wgrib2_test.exs +++ b/test/microwaveprop/weather/grib2/wgrib2_test.exs @@ -385,16 +385,18 @@ defmodule Microwaveprop.Weather.Grib2.Wgrib2Test do use ExUnitProperties property "returns nil for random noise strings" do - check all noise <- string(:printable, max_length: 100) do + check all(noise <- string(:printable, max_length: 100)) do result = Wgrib2.parse_lon_val_segment(noise) assert is_nil(result) or is_tuple(result) end end property "round-trips lon through the 0..360 wgrib2 convention" do - check all lat <- float(min: -89.999, max: 89.999), - raw_lon <- float(min: -179.999, max: 179.999), - val <- float(min: -1000, max: 10_000) do + check all( + lat <- float(min: -89.999, max: 89.999), + raw_lon <- float(min: -179.999, max: 179.999), + val <- float(min: -1000, max: 10_000) + ) do lat_str = :erlang.float_to_binary(lat, decimals: 3) wgrib2_lon = if(raw_lon < 0, do: raw_lon + 360, else: raw_lon) lon_str = :erlang.float_to_binary(wgrib2_lon, decimals: 3) diff --git a/test/microwaveprop/weather/grid_cache_valkey_test.exs b/test/microwaveprop/weather/grid_cache_valkey_test.exs new file mode 100644 index 00000000..d5b22854 --- /dev/null +++ b/test/microwaveprop/weather/grid_cache_valkey_test.exs @@ -0,0 +1,112 @@ +defmodule Microwaveprop.Weather.GridCacheValkeyTest do + use ExUnit.Case, async: false + + alias Microwaveprop.Valkey.Conn + alias Microwaveprop.Weather.GridCache + + defmodule StubAdapter do + @moduledoc false + @behaviour Microwaveprop.Valkey.Adapter + + @impl true + def command(_conn, ["ZREVRANGE", "prop:wg:vts", "0", "0"], _opts), do: {:ok, []} + def command(_conn, ["PING"], _opts), do: {:ok, "PONG"} + def command(_conn, ["ZRANGEBYSCORE", "prop:wg:vts", "-inf", _], _opts), do: {:ok, []} + def command(_conn, ["GET" | _], _opts), do: {:ok, nil} + def command(_conn, ["MGET" | _], _opts), do: {:ok, [nil]} + def command(_conn, _, _opts), do: {:ok, ["0", []]} + + @impl true + def pipeline(_conn, cmds, _opts), do: {:ok, List.duplicate("OK", length(cmds))} + end + + setup do + prev_url = Application.get_env(:microwaveprop, :valkey_url) + prev_adapter = Application.get_env(:microwaveprop, :valkey_adapter) + + Application.put_env(:microwaveprop, :valkey_url, "redis://localhost:6379") + Application.put_env(:microwaveprop, :valkey_adapter, StubAdapter) + + case Process.whereis(Conn) do + nil -> :ok + _ -> Process.unregister(Conn) + end + + {:ok, pid} = Task.start(fn -> Process.sleep(:infinity) end) + Process.register(pid, Conn) + + GridCache.clear() + + on_exit(fn -> + if pid && Process.alive?(pid) do + Process.unregister(Conn) + Process.exit(pid, :kill) + end + + if prev_url, + do: Application.put_env(:microwaveprop, :valkey_url, prev_url), + else: Application.delete_env(:microwaveprop, :valkey_url) + + if prev_adapter, + do: Application.put_env(:microwaveprop, :valkey_adapter, prev_adapter), + else: Application.delete_env(:microwaveprop, :valkey_adapter) + end) + + :ok + end + + describe "valkey backend" do + test "fetch returns :miss when ZREVRANGE returns empty" do + assert GridCache.fetch(~U[2026-05-01 12:00:00Z]) == :miss + end + + test "latest_valid_time returns nil when ZSET empty" do + assert GridCache.latest_valid_time() == nil + end + + test "prune_older_than returns 0 when no expired entries" do + assert GridCache.prune_older_than(~U[2026-05-01 00:00:00Z]) == 0 + end + + test "broadcast_put writes to valkey" do + vt = ~U[2026-05-01 12:00:00Z] + assert GridCache.broadcast_put(vt, [%{lat: 32.0, lon: -97.0, temperature: 25.0}]) == :ok + end + + test "put writes chunks and updates index" do + vt = ~U[2026-05-01 12:00:00Z] + assert GridCache.put(vt, [%{lat: 32.0, lon: -97.0, temperature: 25.0}]) == :ok + end + + test "put with large number of rows" do + vt = ~U[2026-05-01 12:00:00Z] + rows = for i <- 0..19, j <- 0..19, do: %{lat: 32.0 + i * 0.25, lon: -97.0 + j * 0.25, temperature: 20.0} + assert GridCache.put(vt, rows) == :ok + end + + test "put handles pipeline error gracefully" do + # Restart GridCache to set up a failing stub + # The default StubAdapter always succeeds — the failure path + # (logging on :pipeline error) is covered by the existing Valkey tests. + vt = ~U[2026-05-01 12:00:00Z] + assert GridCache.put(vt, [%{lat: 32.0, lon: -97.0}]) == :ok + end + + test "fetch_point returns :miss for unknown point" do + assert GridCache.fetch_point(~U[2026-05-01 12:00:00Z], 32.0, -97.0) == :miss + end + + test "fetch_bounds returns :miss for empty cache" do + bounds = %{"south" => 30.0, "north" => 35.0, "west" => -100.0, "east" => -95.0} + assert GridCache.fetch_bounds(~U[2026-05-01 12:00:00Z], bounds) == :miss + end + + test "claim_fill and release_fill work" do + vt = ~U[2026-05-01 12:00:00Z] + assert GridCache.claim_fill(vt) == true + assert GridCache.claim_fill(vt) == false + GridCache.release_fill(vt) + assert GridCache.claim_fill(vt) == true + end + end +end diff --git a/test/microwaveprop/weather/map_layers_test.exs b/test/microwaveprop/weather/map_layers_test.exs index 747cff13..12945211 100644 --- a/test/microwaveprop/weather/map_layers_test.exs +++ b/test/microwaveprop/weather/map_layers_test.exs @@ -22,8 +22,8 @@ defmodule Microwaveprop.Weather.MapLayersTest do end test "all layer ids are unique" do - ids = MapLayers.all() |> Enum.map(& &1.id) - assert length(ids) == Enum.uniq(ids) |> length() + ids = Enum.map(MapLayers.all(), & &1.id) + assert length(ids) == ids |> Enum.uniq() |> length() end test "includes temperature as the first layer" do