code: def test_unit_spec_changes_do_not_boomerang(
      monkeypatch: pytest.MonkeyPatch, ManagedServerLoop: MSL
  ) -> None:
      application = Application()
      with ManagedServerLoop(application) as server:
          doc = document.Document()
          client_root = UnitsSpecModel()
          doc.add_root(client_root)
  
          client_session = push_session(
              doc,
              session_id=ID("test_unit_spec_changes_do_not_boomerang"),
              url=url(server),
              io_loop=server.io_loop,
          )
          server_session = server.get_session("/", client_session.id)
  
          assert len(server_session.document.roots) == 1
          server_root = next(iter(server_session.document.roots))
  
          assert client_root.distance == 42
          assert server_root.angle == 0
  
          def change_to(new_distance, new_angle):
              got_angry = {}
              got_angry["result"] = None
  
              # trap any boomerang
              def get_angry(message):
                  got_angry["result"] = message
  
              monkeypatch.setattr(
                  client_session, "_handle_patch", get_angry
              )
  
              server_previous_distance = server_root.distance
              server_previous_angle = server_root.angle
  
              # Now modify the client document
              client_root.distance = new_distance
              client_root.angle = new_angle
  
              # wait until server side change made ... but we may not have the
              # boomerang yet
              def server_change_made():
                  return (
                      server_root.distance != server_previous_distance
                      and server_root.angle != server_previous_angle
                  )
  
              client_session._connection._loop_until(server_change_made)
  
              # force a round trip to be sure we get the boomerang if we're going to
              client_session.force_roundtrip()
  
              assert got_angry["result"] is None
  
          change_to(57, 1)
          change_to({"value": 58}, {"value": 2})
          change_to({"field": "foo"}, {"field": "bar"})
          change_to(
              {"value": 59, "units": "screen"},
              {"value": 30, "units": "deg"},
          )
  
          client_session.close()
          server.unlisten()  # clean up so next test can run
          client_session._loop_until_closed()
          assert not client_session.connected
  
imports: from __future__ import annotations
  import pytest
  import asyncio
  import logging
  import sys
  from unittest.mock import MagicMock, patch
  from tornado.httpclient import HTTPError
  import bokeh.document
  from bokeh.application import Application
  from bokeh.application.handlers import FunctionHandler
  from bokeh.client import ClientSession, pull_session, push_session
  from bokeh.core.properties import AngleSpec, Any, Bytes, Dict, DistanceSpec, Instance, Int, Nullable, String
  from bokeh.core.types import ID
  from bokeh.document import Document
  from bokeh.document.events import ModelChangedEvent, TitleChangedEvent
  from bokeh.model import Model
  from bokeh.models import ColumnDataSource, Plot
  from bokeh.util.token import generate_jwt_token
  from tests.support.plugins.managed_server_loop import MSL
  from tests.support.util.env import envset
  from server._util_server import http_get, url, websocket_open, ws_url
label_desc: Use image node_id 3 for calling a global function with example usage: test_unit_spec_changes_do_not_boomerang(monkeypatch, ManagedServerLoop) and returns: unknown