Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best way to assert the contents of a zip archive in Elixir?

Tags:

zip

erlang

elixir

Currently what I am doing:

  1. Testing function by letting it zip a file/directory. Assert it exists.
  2. Using :zip.t and :zip.tt let it list down the contents of the zip folder to see if it's what I am expecting.

Somehow I think I am missing something. Is it better to test with :zip.table ? The function looks confusing. Can someone provide an example of how to use it ? Below is an example of the output I got to, but I can't figure out how to make this into a test ? Is md5sum a better test for zip archives ?

iex(4)> :zip.table('testing.zip')
{:ok,
 [{:zip_comment, []},
  {:zip_file, 'mix.exs',
   {:file_info, 930, :regular, :read_write, {{2015, 7, 15}, {2, 11, 9}},
    {{2015, 7, 15}, {2, 11, 9}}, {{2015, 7, 15}, {2, 11, 9}}, 54, 1, 0, 0, 0, 0,
    0}, [], 0, 444},
  {:zip_file, 'mix.lock',
   {:file_info, 332, :regular, :read_write, {{2015, 7, 15}, {2, 9, 6}},
    {{2015, 7, 15}, {2, 9, 6}}, {{2015, 7, 15}, {2, 9, 6}}, 54, 1, 0, 0, 0, 0,
    0}, [], 481, 152}]}
like image 728
Muhammad Lukman Low Avatar asked Jul 14 '15 18:07

Muhammad Lukman Low


1 Answers

The :zip module from Erlang is not really easy to work with, I'll try to break it down for you.

First, we will need an appropriate representation of the zip_file record in order from Erlang to be able to properly work with it. Otherwise, we would have to do matches on tuples with a lot of elements which would just clutter our code unnecessarily. The following module is heavily based on the File.Stat implementation from Elixir and will allow us to access the values in those unwieldy tuples with simple dot notation.

require Record

defmodule Zip.File do
  record = Record.extract(:zip_file, from_lib: "stdlib/include/zip.hrl")
  keys   = :lists.map(&elem(&1, 0), record)
  vals   = :lists.map(&{&1, [], nil}, keys)
  pairs  = :lists.zip(keys, vals)

  defstruct keys

  def to_record(%Zip.File{unquote_splicing(pairs)}) do
    {:zip_file, unquote_splicing(vals)}
  end

  def from_record(zip_file)
  def from_record({:zip_file, unquote_splicing(vals)}) do
    %Zip.File{unquote_splicing(pairs)}
    |> Map.update!(:info, fn(info) -> File.Stat.from_record(info) end)
  end
end

We can now build a small wrapper class around the Erlang zip module. It does not wrap all methods, just the ones we'll use here. I also added a list_files/1 function that only returns files, excluding directories and comments from the listing.

defmodule Zip do
  def open(archive) do
    {:ok, zip_handle} = :zip.zip_open(archive)
    zip_handle
  end

  def close(zip_handle) do
    :zip.zip_close(zip_handle)
  end

  def list_dir(zip_handle) do
    {:ok, result} = :zip.zip_list_dir(zip_handle)
    result
  end

  def list_files(zip_handle) do
    list_dir(zip_handle)
    |> Enum.drop(1)
    |> Enum.map(&Zip.File.from_record/1)
    |> Enum.filter(&(&1.info.type == :regular))
  end
end

Suppose we have the following zip archive for testing:

cd /tmp
touch foo bar baz
zip archive.zip foo bar baz

Now you can assert the file names inside the zip archive:

test "files are in zip" do
  zip = Zip.open('/tmp/archive.zip')
  files = Zip.list_files(zip) |> Enum.map(&(&1.name))
  Zip.close(zip)
  assert files == ['foo', 'bar', 'baz']
end

I'll leave further operations and assertions on the zip archive for you to implement, and hope this gets you started.

like image 73
Patrick Oscity Avatar answered Nov 15 '22 08:11

Patrick Oscity