code: def test_conv1d_stack(
      layers: Union[None, list],
      num_layers: Union[None, int],
      dropout: float,
  ) -> None:
      # make test repeatable
      torch.manual_seed(RANDOM_SEED)
  
      # setup synthetic input tensor for test
      input = torch.randn(
          [BATCH_SIZE, SEQ_SIZE, HIDDEN_SIZE], dtype=torch.float32
      )
  
      conv1_stack = Conv1DStack(
          in_channels=HIDDEN_SIZE,
          out_channels=NUM_FILTERS,
          max_sequence_length=SEQ_SIZE,
          layers=layers,
          num_layers=num_layers,
          default_num_filters=NUM_FILTERS,
          default_dropout=dropout,
      )
  
      # check for correct stack formation
      if layers is None:
          assert (
              len(conv1_stack.stack) == 6
              if num_layers is None
              else num_layers
          )
      else:
          # custom layer specification
          assert len(conv1_stack.stack) == len(layers)
          assert conv1_stack.stack[0].out_channels == NUM_FILTERS - 2
          assert conv1_stack.stack[1].out_channels == NUM_FILTERS + 2
  
      # generate output tensor
      out_tensor = conv1_stack(input)
  
      # check for correct output class
      assert isinstance(out_tensor, torch.Tensor)
  
      assert out_tensor.size()[1:] == conv1_stack.output_shape[:]
  
      # check for correct output shape
      last_module = conv1_stack.stack[-1]
      output_seq_size = expected_seq_size(
          seq_size=last_module.input_shape[0],
          padding=last_module.padding,
          kernel_size=last_module.kernel_size,
          stride=last_module.stride,
          dilation=last_module.dilation,
          pool_size=last_module.pool_size,
          pool_padding=last_module.pool_padding,
          pool_stride=last_module.pool_strides,
      )
      if layers is None:
          # default stack setup
          assert out_tensor.size() == (
              BATCH_SIZE,
              output_seq_size,
              NUM_FILTERS,
          )
      else:
          # custom stack setup
          assert out_tensor.size() == (
              BATCH_SIZE,
              output_seq_size,
              NUM_FILTERS + 2,
          )
  
      # check for parameter updates
      target = torch.randn(conv1_stack.output_shape)
      _, tpc, upc, not_updated = check_module_parameters_updated(
          conv1_stack, (input,), target
      )
      if dropout == 0:
          # all trainable parameters should be updated
          assert tpc == upc, (
              f"All parameter not updated. Parameters not updated: {not_updated}"
              f"\nModule structure:\n{conv1_stack}"
          )
      else:
          # with specified config and random seed, non-zero dropout update parameter count could take different values
          assert (tpc == upc) or (upc == 1), (
              f"All parameter not updated. Parameters not updated: {not_updated}"
              f"\nModule structure:\n{conv1_stack}"
          )
  
file_path: ludwig/tests/ludwig/modules/test_convolutional_modules.py
imports: from typing import Callable, Dict, List, Optional, Tuple, Union
  import pytest
  import torch
  from ludwig.modules.convolutional_modules import Conv1DLayer, Conv1DStack, Conv2DLayer, Conv2DLayerFixedPadding, Conv2DStack, ParallelConv1D, ParallelConv1DStack, ResNet, ResNetBlock, ResNetBlockLayer, ResNetBottleneckBlock
  from ludwig.utils.image_utils import get_img_output_shape
  from tests.integration_tests.parameter_update_utils import check_module_parameters_updated
label_desc: Use image node_id 3 for calling a global function with example usage: test_conv1d_stack(layers, num_layers, dropout) without return types