Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't test multiple file upload #3480

Open
lessless opened this issue Oct 23, 2024 · 4 comments
Open

Can't test multiple file upload #3480

lessless opened this issue Oct 23, 2024 · 4 comments

Comments

@lessless
Copy link

lessless commented Oct 23, 2024

Environment

  • Elixir version (elixir -v): 1.17.3
  • Phoenix version (mix deps): 1.7.14
  • Phoenix LiveView version (mix deps): tested on 0.20.17 and the latest 1.0.0-rc.7
  • Operating system: macos
  • Browsers you attempted to reproduce this bug on (the more the merrier): n/a
  • Does the problem persist after removing "assets/node_modules" and trying again? Yes/no: n/a

Actual behavior

The second render_upload raises an error when I try to test multiple files upload

defp handle_progress(:documents, %{done?: true}, socket) do
    case uploaded_entries(socket, :documents) do
      {[_ | _] = _completed, []} ->
        
      consume_uploaded_entries(socket, :documents, fn %{path: path}, entry ->
          :ok = upload_file(entry, path, socket.assigns.current_practice) 
          {:ok, entry}
        end)
        
        {:noreply, start_async(socket, :fetch_documents, fn -> documents_data() end)}

      _in_progress ->
        {:noreply, socket}
    end
  end

  defp handle_progress(:documents, %{done?: false}, socket) do
    {:noreply, socket}
  end
test "can upload multiple documents", %{conn: conn, practice: practice} do
  {:ok, view, _html} = live(conn, ~p"/upload")
  render_async(view)
  
  documents =
    file_input(view, "#manual-document-upload-form", :documents, [
      %{
        last_modified: 1_594_171_879_000,
        name: "a document.tif",
        content: <<73, 73, 42, 0, 8, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0>>,
        size: 16,
        type: "image/tiff"
      },
      %{
        last_modified: 1_594_171_879_000,
        name: "another document.tif",
        content: <<73, 73, 42, 0, 8, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0>>,
        size: 16,
        type: "image/tiff"
      }
    ])

  render_upload(documents, "a document.tif")
  IO.puts "after first render_upload"
 
  render_upload(documents, "another document.tif")
  IO.puts "after second render_upload"

  view
  |> test_id("view-action-items-checkbox")
  |> render_click()

  assert render_async(view) =~ "successfully uploaded"
end

the "after second render_uploa" is never printed the test fails with the error

11:31:36.156 [error] GenServer #PID<0.681.0> terminating
** (stop) exited in: GenServer.call(#PID<0.689.0>, :consume_done, :infinity)
    ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
    (elixir 1.17.3) lib/gen_server.ex:1128: GenServer.call/3
    (phoenix_live_view 0.20.17) lib/phoenix_live_view/upload_channel.ex:26: Phoenix.LiveView.UploadChannel.consume/3
    (elixir 1.17.3) lib/enum.ex:1703: Enum."-map/2-lists^map/1-1-"/2
    (elixir 1.17.3) lib/enum.ex:1703: Enum."-map/2-lists^map/1-1-"/2
    (mailroom 0.1.0) lib/mailroom_web/live/list_views/pages/upload_live.ex:186: MailroomWeb.UploadLive.handle_progress/3
    (phoenix_live_view 0.20.17) lib/phoenix_live_view/channel.ex:181: anonymous fn/4 in Phoenix.LiveView.Channel.handle_info/2
    (phoenix_live_view 0.20.17) lib/phoenix_live_view/channel.ex:1422: Phoenix.LiveView.Channel.write_socket/4
    (phoenix_live_view 0.20.17) lib/phoenix_live_view/channel.ex:174: Phoenix.LiveView.Channel.handle_info/2
    (stdlib 6.1.2) gen_server.erl:2345: :gen_server.try_handle_info/3
    (stdlib 6.1.2) gen_server.erl:2433: :gen_server.handle_msg/6
    (stdlib 6.1.2) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
Last message: %Phoenix.Socket.Message{topic: "lv:phx-GAEOVlW_stZitAlC", event: "progress", payload: %{"entry_ref" => "2819", "progress" => 100, "ref" => "phx-GAEOVmA5Q6Fv-Api"}, ref: "5", join_ref: 0}
11:31:36.168 [error] GenServer #PID<0.679.0> terminating
** (stop) exited in: GenServer.call(#PID<0.681.0>, {:phoenix, :ping}, :infinity)
    ** (EXIT) exited in: GenServer.call(#PID<0.689.0>, :consume_done, :infinity)
        ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
Last message: {:EXIT, #PID<0.643.0>, {{:noproc, {GenServer, :call, [#PID<0.689.0>, :consume_done, :infinity]}}, {GenServer, :call, [#PID<0.681.0>, {:phoenix, :ping}, :infinity]}}}


  1) test can upload multiple documents (MailroomWeb.ListViews.Pages.Dyad.PreparingListPageLiveTest)
     test/mailroom_web/live/list_views/pages/dyad/preparing_list_page_live_test.exs:248
     ** (EXIT from #PID<0.643.0>) exited in: GenServer.call(#PID<0.681.0>, {:phoenix, :ping}, :infinity)
         ** (EXIT) exited in: GenServer.call(#PID<0.689.0>, :consume_done, :infinity)
             ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started

Expected behavior

I can test multiple file upload

UPDATE:

what seems to happen is that render_tree tries to communicate with exited LV process
https://github.com/phoenixframework/phoenix_live_view/blob/main/lib/phoenix_live_view/test/live_view_test.ex#L1091

as UploadClient.chunk(upload, entry_name, percent, proxy_pid(upload.view)) (https://github.com/phoenixframework/phoenix_live_view/blob/main/lib/phoenix_live_view/test/live_view_test.ex#L1972C12-L1972C84) returns {:ok, :closed} during the second render_submit

[(phoenix_live_view 0.20.17) lib/phoenix_live_view/test/live_view_test.ex:1970: Phoenix.LiveViewTest.render_chunk/3]
UploadClient.chunk(upload, entry_name, percent, proxy_pid(upload.view)) #=> {:ok, :closed}
@lessless lessless changed the title Race condition when testing mulitple file upload Can't test multiple file upload Oct 23, 2024
@dersmon
Copy link
Contributor

dersmon commented Oct 30, 2024

I got the same issue when using file_input/4 with more than one entry. Slight difference in that I see the "Second upload" message and I use render_submit/1 on the form:

(...)

upload_input =
  file_input(process, "#upload-form", :images, [
    %{
      last_modified: 1_594_171_879_000,
      name: "valid_file_a.jpg",
      content: "dummy file content"
    },
    %{
      last_modified: 1_594_171_879_000,
      name: "valid_file_b.jpg",
      content: "dummy file content"
    }
  ])

render_upload(upload_input, "valid_file_a.jpg")
IO.inspect("First upload")
render_upload(upload_input, "valid_file_b.jpg")
IO.inspect("Second upload")

process
|> form("#upload-form", [])
|> render_submit()

(...)

My error message:

"First upload"
"Second upload"
14:05:17.694 [error] GenServer #PID<0.408.0> terminating
** (stop) exited in: GenServer.call(#PID<0.419.0>, :consume_start, :infinity)
    ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
    (elixir 1.17.2) lib/gen_server.ex:1128: GenServer.call/3
    (phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/upload_channel.ex:15: Phoenix.LiveView.UploadChannel.consume/3
    (elixir 1.17.2) lib/enum.ex:1703: Enum."-map/2-lists^map/1-1-"/2
    (elixir 1.17.2) lib/enum.ex:1703: Enum."-map/2-lists^map/1-1-"/2
    (my_app 1.0.0-rc3) lib/my_app_web/live/archive_upload_component.ex:131: MyAppWeb.ArchiveUploadComponent.handle_event/3
    (phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/channel.ex:741: anonymous fn/4 in Phoenix.LiveView.Channel.inner_component_handle_event/4
    (telemetry 1.3.0) </path/to/project/>deps/telemetry/src/telemetry.erl:324: :telemetry.span/3
    (phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/diff.ex:209: Phoenix.LiveView.Diff.write_component/4
    (phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/channel.ex:662: Phoenix.LiveView.Channel.component_handle/4
    (stdlib 6.0.1) gen_server.erl:2173: :gen_server.try_handle_info/3
    (stdlib 6.0.1) gen_server.erl:2261: :gen_server.handle_msg/6
    (stdlib 6.0.1) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
Last message: %Phoenix.Socket.Message{topic: "lv:phx-GAM8yUEFIExknAEl", event: "event", payload: %{"cid" => 3, "event" => "submit_selected_images", "type" => "form", "value" => "images="}, ref: "4", join_ref: 0}
14:05:17.764 [error] GenServer #PID<0.406.0> terminating
** (stop) exited in: GenServer.call(#PID<0.419.0>, :consume_start, :infinity)
    ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
Last message: {:EXIT, #PID<0.403.0>, {:noproc, {GenServer, :call, [#PID<0.419.0>, :consume_start, :infinity]}}}

@chrismccord
Copy link
Member

@lessless can you share your full example. Looks like you're using auto_upload: true? I need to dig in deeper, but at first glance, you are auto uploader both files and then calling consume_uploaded_entries when you see one is finished, which will consume all completed entries. I think what you wanted there was consume_uploaded_entry. I think you're consuming and nuking the 2nd one before you try to render_upload it in the test.

@dersmon
Copy link
Contributor

dersmon commented Nov 12, 2024

Just for completeness sake:

My upload is basically lifted from the examples.

socket
|> allow_upload(:images,
  accept: ~w(.jpg .tif),
  max_entries: @max_upload_count,
  max_file_size: @max_upload_size
)

And another observation:

If I remove one of the render_upload, I get the following error.

     ** (EXIT from #PID<0.411.0>) an exception was raised:
         ** (ArgumentError) cannot consume uploaded files when entries are still in progress
             (phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/upload.ex:248: Phoenix.LiveView.Upload.consume_uploaded_entries/3
             (my_app 1.0.0-rc3) lib/my_app/live/archive_upload_component.ex:135: MyAppWeb.ArchiveUploadComponent.handle_event/3
             (phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/channel.ex:741: anonymous fn/4 in Phoenix.LiveView.Channel.inner_component_handle_event/4
             (telemetry 1.3.0)  </path/to/project/>/deps/telemetry/src/telemetry.erl:324: :telemetry.span/3
             (phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/diff.ex:209: Phoenix.LiveView.Diff.write_component/4
             (phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/channel.ex:662: Phoenix.LiveView.Channel.component_handle/4
             (stdlib 6.0.1) gen_server.erl:2173: :gen_server.try_handle_info/3
             (stdlib 6.0.1) gen_server.erl:2261: :gen_server.handle_msg/6
             (stdlib 6.0.1) proc_lib.erl:329: :proc_lib.init_p_do_apply/3

@lessless
Copy link
Author

lessless commented Nov 12, 2024

@chrismccord you're spot on about the auto_upload: true.

I need to dig in deeper, but at first glance, you are auto uploader both files and then calling consume_uploaded_entries when you see one is finished, which will consume all completed entries. I think what you wanted there was consume_uploaded_entry.

I might have misunderstood the documentation then. I use uploaded_entries to run consume_uploaded_entries only after all files were uploaded as indicated in the documentation.

 def mount(_params, _session, socket) do
    {:ok,
     socket
     |> allow_upload(:documents, accept: ~w(.pdf .tif .tiff), max_entries: 100, auto_upload: true, progress: &handle_progress/3)}
  end
  defp handle_progress(:documents, %{done?: true}, socket) do
    case uploaded_entries(socket, :documents) do
      {[_ | _] = _completed, []} ->
        consume_uploaded_entries(socket, :documents, fn %{path: path}, entry ->
          case upload_file(entry, path, socket.assigns.current_practice) do
            {:ok, _} ->
              Toaster.toast(socket.id, :info, "#{entry.client_name} successfully uploaded")

            {:error, _} ->
              Toaster.toast(socket.id, :error, "Failed to upload #{entry.client_name}")
          end

          {:ok, entry}
        end)


        {:noreply, socket}

      _in_progress ->
        {:noreply, socket}
    end
  end

  defp handle_progress(:documents, %{done?: false}, socket) do
    {:noreply, socket}
  end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants