How to implement a frozen test environment?
TeamCity has a Shared Resources build feature which allows you to define a resource which multiple Build Definitions depend upon it.  Build Definitions can either require a Read Lock or a Write Lock, you can also define if these locks are exclusive or allow a degree of parallelism.

If we make the following assumptions about a shared environment named PreProd:


A Shared Resource exists named "PreProd".
All build definitions, such as deployments, that make any change to that environment take an exclusive Write Lock on "PreProd".
All build definitions, such as non-restrictive tests, that perform read-only operations on the environment take a non-exclusive Read Lock on "PreProd".
TeamCity is the only process that can do anything on PreProd, albeit perhaps via another tool.


Therefore the following are true:


When a deployment is happening then nothing else can use PreProd, they will be queued.
When a test is running, any deployment would be queued until the tests complete.


You can use a similar mechanism with Jenkins using the Exclusion plugin.  In fact, you could build this functionality into any process using locking or semaphore - for example Apache ZooKeeper or HashiCorp Consul.